位图法

定义位图法即 bitmap,就是用一个 bit 位来标记某个元素对应的 Value,而 Key 即是该元素。由于采用了 Bit 为单位来存储数据,因此在内存占用方面,可以大大节省。适用于大规模数据,但数据状态不是很多的情况。通常是用来判断某个数据存不存在。实现应用100亿整型数据去重整型数据为 32
阅读更多

如何用 random9 求 random10

大意

如果有一个 random9() 函数,可以产生 1-9 的 整数随机数,请利用 random9 实现 random10 函数,返回 1-10 的整数随机数

要点

random9 只能随机产生 1-9 范围内的数,也就是 1-9 出现的概率是一样的;而如果要随机产生 1-10 范围的整数,就需要 1-10 出现的概率是一样的。

所以问题就变成了:如何通过等概率出现的 1-9 产生等概率的 1-10 ?

解决

以一种马后炮的思想尝试解释一下,看能不能说服自己。

random9 显然产生不了 10,所以肯定需要用到加/乘法,那如何产生 10?

  • random9 + n
    如果只用加法,这样便可以随机产生[n+1, n+9]范围的整数,可以达到10,但是肯定不能产生[1, 10]范围内的数,所以这样不行。

  • random9 * n
    如果只用乘法,这样便可以随机产生[n, 2n, 3n, …, 9n],这样产生的数显然也不可能做到随机产生[1, 10],因为中间存在空位,并且无法保证是等概率的。

要想等概率产生[1, 10]范围的数,可以利用等概率产生[1, 20]范围的整数,然后%10 + 1。这里有个重点,怎么样才能等概率?

既然 random9 加/乘一个固定的数,不能达到目的,那么如果加/乘一个不固定但是等概率出现的某一范围的数(也就是 random9)是否可行呢?

  • random9 + random9
    能出现的最大数:9 + 9 = 18;能出现的最小数:1 + 1 = 2
    先考虑能否等概率出现10,先看2-18是否为等概率出现的。
    出现2、18的概率都是1/81,但出现3的概率是2/81,即1+2和2+1,这个就不是等概率出现的了,所以不能做到随机产生。
  • random9 * random9
    最大数:81,最小数:1。
    是否连续?否。
    是否等概率?否。最大、小数的概率是1/81。但是9的概率是3/81。
  • random9 * n + random9
    random9 * n 可等概率产生 1n,2n,3n…9n,如果加上 random9,是否等概率取决于1n和2n之间的间距,如果刚好等于9,那么加上random9就有希望产生连续、并且等概率的整数,范围为:[10, 90]。

如何通过等随机产生的 [10, 90] 来随机产生 [1, 10]?
将随机产生的 [10, 90] 限制为 [10, 89],然后模10,便可以获得随机产生的[0, 9],然后再加1,即可得到随机产生的[1, 10]。

总结为:先通过 random9 * 9 + random9 随机产生[10, 90]范围的整数,然后限制随机产生 [10, 89],再模10,得到[0, 9],再加1即可得到[1, 10]。

所以最终的代码:

1
2
while ((result = 9 * random9() + random9()) >= 90);
return result % 10 + 1;

但是,我看到的答案是这样的,还不太明白这两者之间有什么差别。

1
2
3
4
5
6
7
public int rand10() {
int result;
do {
result = rand9()+(rand9()-1)*9;
} while (result > 80);
return 1 + (result - 1) % 10;
}

更加简单粗暴的方式

但是这个问题是由 random7 求 random10,不过思想是相通的。

感觉这一种方式更加通俗易懂,获取一维数组是随机的,获取第二维数组也是随机,整个过程都是随机的。

1
2
3
4
5
6
7
8
9
10
11
12
public static int random10() {
int[][] seed = { { 1, 2, 3, 4, 5, 6, 7 }, { 8, 9, 10, 11, 12, 13, 14 },
{ 15, 16, 17, 18, 19, 20, 11 }, { 22, 23, 24, 25, 26, 27, 28 },
{ 29, 30, 31, 32, 33, 34, 35 }, { 36, 37, 38, 39, 40, 41, 42 },
{ 43, 44, 45, 46, 47, 48, 49 } };

int result;
do {
result = seed[random7() - 1][random7() - 1];
} while (result > 40);
return result % 10 + 1;
}

如何快速确认链表上是否存在环

这个问题遇到过很多次了,但是并不是说每次都很清楚。所以这次用golang的代码来实现一遍,加深理解与记忆。如果一个链表上不存在环,那么一定能够遍历完链表中所有的Node节点;如果存在环,那么可以想象成存在一个圆形操场。在一个圆里面,如果有两个人,行走的速度不一样,那么一定会有相遇的那一刻。最佳的解法
阅读更多

leetcode回溯法题目解法若干

N皇后问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import java.util.ArrayList;
import java.util.List;

/*
* @lc app=leetcode id=51 lang=java
*
* [51] N-Queens
*/
public class Solution {

List<List<String>> result = new ArrayList<>();

private static int total = 0;

public List<List<String>> solveNQueens(int n) {
total = n;
int[][] board = new int[n][n];
backtrack(board, 1);
return result;
}

private void backtrack(int[][] board, int idx) {
if (idx > total) {
// ok
result.add(transform(board));
return;
}

for (int i = 0; i < total; i ++) {
if (isOk(board, idx - 1, i)) {
//
board[idx - 1][i] = 1;
backtrack(board, idx + 1);
board[idx - 1][i] = 0;
}
}
}

private boolean isOk(int[][] board, int xx, int yy) {
for (int i = 0; i < total; i++) {
if (board[xx][i] == 1) {
return false;
}
if (board[i][yy] == 1) {
return false;
}
}
int x = xx, y = yy;
while (x < total && y >= 0)
if (board[x ++][y --] == 1)
return false;
x = xx; y = yy;
while (x < total && y < total)
if (board[x ++][y ++] == 1)
return false;
x = xx; y = yy;
while (x >= 0 && y >= 0)
if (board[x --][y --] == 1)
return false;
x = xx; y = yy;
while (x >=0 && y < total)
if (board[x --][y ++] == 1)
return false;
// System.out.println("OK");
return true;
}

private List<String> transform(int[][] board) {
List<String> oneAnswer = new ArrayList<>();
for (int i = 0; i < board.length; i ++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < board[i].length; j ++) {
sb.append(board[i][j] == 0 ? '.' : 'Q');
}
oneAnswer.add(sb.toString());
}
// showAnswer(oneAnswer);
return oneAnswer;
}

private void showAnswer(List<String> answer) {
System.out.println();
for (String line : answer) {
System.out.println(line);
}
}

public static void main(String[] args) {
List<List<String>> result = new Solution().solveNQueens(4);
System.out.println(result.size());
}
}

Letter Combinations of a Phone Number

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.ArrayList;
import java.util.List;

/*
* @lc app=leetcode id=17 lang=java
*
* [17] Letter Combinations of a Phone Number
*/
class Solution {
private int[][] maps = {
{},
{},
{'a', 'b', 'c'},
{'d', 'e', 'f'},
{'g', 'h', 'i'},
{'j', 'k', 'l'},
{'m', 'n', 'o'},
{'p', 'q', 'r', 's'},
{'t', 'u', 'v'},
{'w', 'x', 'y', 'z'}
};
List<String> result = new ArrayList<>();
public List<String> letterCombinations(String digits) {
backtrack(digits, "", 0);
return result;
}
private void backtrack(String digits, String sb, int idx) {
if (idx >= digits.length()) {
if (sb.length() > 0)
result.add(sb.toString());
return;
}
int mapsIdx = digits.charAt(idx) - '0';
for (int i = 0; i < maps[mapsIdx].length; i ++) {
backtrack(digits, sb + (char) maps[mapsIdx][i], idx + 1);
}
}
}

Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

1
2
3
4
5
6
7
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
List<String> sets = new ArrayList<>();

// 22
public List<String> generateParenthesis(int n) {
produce(2 * n, 0, n, n, "");
return sets;
}

private void produce(int blank, int sum, int lcnt, int rcnt, String seq) {
if (sum < 0)
return;

if (blank == 0) {
sets.add(seq);
return;
}

if (lcnt > 0) {
produce(blank - 1, sum + 1, lcnt - 1, rcnt, seq + "(");
}

if (rcnt > 0) {
produce(blank - 1, sum - 1, lcnt, rcnt - 1, seq + ")");
}
}
}

Sudoku Solver

Write a program to solve a Sudoku puzzle by filling the empty cells.

A sudoku solution must satisfy all of the following rules:

Each of the digits 1-9 must occur exactly once in each row.

Each of the digits 1-9 must occur exactly once in each column.

Each of the the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.

Empty cells are indicated by the character ‘.’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
* @lc app=leetcode id=37 lang=java
*
* [37] Sudoku Solver
*/
public class Solution {
private static class Point {
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

@Override
public String toString() {
return "<" + x + ", " + y + ">";
}
}

static char[][] data = {
{'5', '3', '.', '.', '7', '.', '.', '.', '.'},
{'6', '.', '.', '1', '9', '5', '.', '.', '.'},
{'.', '9', '8', '.', '.', '.', '.', '6', '.'},
{'8', '.', '.', '.', '6', '.', '.', '.', '3'},
{'4', '.', '.', '8', '.', '3', '.', '.', '1'},
{'7', '.', '.', '.', '2', '.', '.', '.', '6'},
{'.', '6', '.', '.', '.', '.', '2', '8', '.'},
{'.', '.', '.', '4', '1', '9', '.', '.', '5'},
{'.', '.', '.', '.', '8', '.', '.', '7', '9'}
};

public static void main(String[] args) {
new Solution().solveSudoku(data);
printBoard(data, "最终结果");
}

private static void printBoard(char[][] data, String msg) {
System.out.println("\n" + msg + ":");
for (int i = 0; i < data.length; i ++) {
for (int j = 0; j < data[i].length; j++) {
System.out.print(data[i][j] + " ");
}
System.out.println();
}
System.out.println();
}

boolean over = false;

public void solveSudoku(char[][] board) {
backtrack(board, nextPoint(board, new Point(0, 0)));
}

private void backtrack(char[][] board, Point focus) {
if (over || isOver(board)) {
over = true;
return;
}

if (focus == null) {
over = true;
return;
}

for (int val = 1; val <= 9 && !over; val++) {
board[focus.x][focus.y] = (char) ('0' + val);
if (isValid(board, focus, (char) ('0' + val))) {
backtrack(board, nextPoint(board, focus));
}
}
if (!over)
board[focus.x][focus.y] = '.';
}

private boolean isValid(char[][] board, Point p, char val) {
// 横向
for (int i = 0; i <= 8; i++) {
if (i != p.y && board[p.x][i] == val)
return false;
}
// 纵向
for (int i = 0; i <= 8; i++) {
if (i != p.x && board[i][p.y] == val)
return false;
}
// 方形
for (int i = (p.x / 3 * 3); i <= (p.x / 3 * 3) + 2; i++) {
for (int j = (p.y / 3 * 3); j <= (p.y / 3 * 3) + 2; j++) {
if (i != p.x && j != p.y && board[i][j] == val)
return false;
}
}
// printBoard(board, "位置" + p + ", 对值<" + (char)val + ">合适");
return true;
}

private boolean isOver(char[][] board) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
if (board[i][j] == '.')
return false;
}
}
return true;
}

private Point nextPoint(char[][] board, Point p) {
Point returnPoint = null;
int x = p.x, y = p.y;
while (x <= 8 && returnPoint == null) {
for (int i = y; i <= 8; i++) {
if (board[x][i] == '.') {
returnPoint = new Point(x, i);
break;
}
}
y = 0;
x ++;
}
// System.out.println(p + " -> " + returnPoint);
return returnPoint;
}
}

Combination Sum

相关问题:Combination Sum II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

import java.util.ArrayList;
import java.util.List;

/*
* @lc app=leetcode id=39 lang=java
*
* [39] Combination Sum
*/
class Solution {

List<List<Integer>> result = new ArrayList();

private int maxInt = 0;

public List<List<Integer>> combinationSum(int[] candidates, int target) {
sort(candidates);
maxInt = candidates[candidates.length - 1] + 1;
backtrack(candidates, target, new ArrayList<>());
return result;
}

private void sort(int[] arr) {
for (int i = 0; i < arr.length; i ++) {
for (int j = i; j < arr.length; j ++) {
if (arr[i] > arr[j]) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}

private void backtrack(int[] candidates, int target, List<Integer> answer) {
if (target == 0) {
// find the answer
// showArray(answer);
if (!isDuplicated(result, answer, candidates))
result.add(new ArrayList<>(answer));
else {
// System.out.println("Duplicated");
}
} else if (target < 0) {
return;
}

for (int i = 0; i < candidates.length; i++) {
answer.add(candidates[i]);
backtrack(candidates, target - candidates[i] , answer);
answer.remove(answer.size() - 1);
}
}

private void showArray(List<Integer> arr) {
System.out.print("[");
for (int i = 0; i < arr.size(); i ++) {
System.out.print(String.valueOf(arr.get(i)) + (i == arr.size() - 1 ? "]" : ", "));
}
System.out.println();
}

private boolean isDuplicated(List<List<Integer>> result, List<Integer> oneAnswer, int[] candidates) {
for (List<Integer> r : result) {
if (r.size() == oneAnswer.size()) {
int[] sr = new int[maxInt];
int[] si = new int[maxInt];
for (int i = 0; i < r.size(); i ++) {
// System.out.println(r.get(i) + " + 1");
sr[r.get(i)] ++;
// System.out.println(oneAnswer.get(i) + " + 1");
si[oneAnswer.get(i)] ++;
}
boolean isDuplicatedWithThisResult = true;
for (int i = 0; i < candidates.length; i ++) {
// System.out.println(candidates[i] + "," + sr[candidates[i]] + "," + si[candidates[i]]);
if (sr[candidates[i]] != si[candidates[i]]) {
isDuplicatedWithThisResult = false;
continue;
}
}
if (isDuplicatedWithThisResult)
return true;
}
}
return false;
}

public static void main(String[] args) {
int [] arr = {8,7,4,3};
new Solution().combinationSum(arr, 11);
}
}

Permutations

相关问题:Permutations II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
List<List<Integer>> result = new ArrayList();

private boolean[] USAGE;

public List<List<Integer>> permute(int[] nums) {
USAGE = new boolean[nums.length];
List<Integer> answer = new ArrayList();
backtrack(nums, 0, answer);
return result;
}

private void backtrack(int[] nums, int idx, List<Integer> answer) {
if (idx >= nums.length) {
result.add(new ArrayList(answer));
return;
}
for (int i = 0; i < nums.length; i ++) {
if (!USAGE[i]) {
answer.add(nums[i]);USAGE[i] = true;
backtrack(nums, idx + 1, answer);
answer.remove(answer.size() - 1);USAGE[i] = false;
}
}
}
}

Permutation Sequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
private boolean[] usage;
private String result;
private int targetIdx, currentIdx;
private boolean over = false;
public String getPermutation(int n, int k) {
targetIdx = k;
usage = new boolean[n + 1];
StringBuilder sb = new StringBuilder();
backtrack(n, 1, sb);
return result;
}

private void backtrack(int n, int idx, StringBuilder sb) {
if (over)
return;
if (idx > n) {
currentIdx ++;
if (currentIdx == targetIdx) {
result = sb.toString();
over = true;
}
return ;
}
for (int i = 1; i <= n; i ++) {
if (over)
break;
if (!usage[i]) {
sb.append(i);usage[i] = true;
backtrack(n, idx + 1, sb);
sb.deleteCharAt(sb.length() - 1);usage[i] = false;
}
}
}
}

Combinations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
private List<List<Integer>> result = new ArrayList();
private boolean[] USAGE;
private int depth;

public List<List<Integer>> combine(int n, int k) {
depth = k;
USAGE = new boolean[n + 1];
backtrack(1, n, 1, new ArrayList<>());
return result;
}

private void backtrack(int startIdx, int n, int idx, List<Integer> answer) {
if (idx > depth) {
result.add(new ArrayList(answer));
return;
}
for (int i = startIdx; i <= n; i ++) {
if (!USAGE[i]) {
answer.add(i);USAGE[i] = true;
backtrack(i + 1, n, idx + 1, answer);
answer.remove(answer.size() - 1);USAGE[i] = false;
}
}
}
}

【leetcode】21. Merge Two Sorted Lists

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.
Example:
Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

题目属于简单类型,但是好久没写这种题了,再加上C语言也很久没写过了,费了很久。。。
思路很简单,先以一个为基准,然后再将另外一个逐个插入其中(然而开始的时候并不是这么想的,用的某种骚操作,结果发现写着写着搞不定了,这种写代码的方式果然不行呀)。感觉还可以有提高的地方,比如用个二分什么的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
#define FINISHED -1
#define UNFINISHED -2
#define SKIP 0
void showList(struct ListNode* l) {
printf("[");
while (l != NULL) {
printf("%d", l->val);
if (l->next != NULL) {
printf(",");
}
l = l->next;
}
printf("]\n");
}

int insertNode(struct ListNode* l, struct ListNode* node) {
struct ListNode* tmp;
struct ListNode* pre = NULL;
struct ListNode* tp = l;
int handled = 0;
// printf("Before insert: ");showList(l);
while (tp != NULL) {
// printf("tp->val = %d, node->val = %d\n", tp->val, node->val);
if (node->val <= tp->val) {
if (tp == l) {
int ti = l->val;
l->val = node->val;
node->val = ti;

tmp = l->next;
l->next = node;
node->next = tmp;
} else {
pre->next = node;
node = node->next;
pre->next->next = tp;
}
handled = 1;
break;
}
pre = tp;
tp = tp->next;
}
if (!handled) {
pre->next = node;
return FINISHED;
}
// printf("After insert: ");showList(l);
return UNFINISHED;
}

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode* root = NULL;

if (l1 == NULL || l2 == NULL) {
if (root == NULL)
root = (l1 == NULL ? l2 : l1);
return root;
}
if (root == NULL) {
root = l1;
}
l1 = l2;
int finished = UNFINISHED;
while (l1 != NULL) {
struct ListNode* next = l1->next;
int res = insertNode(root, l1);
switch(res){
case FINISHED: return root;
case UNFINISHED:
case SKIP:
l1 = next;
break;
}
// printf("Wait to insert: ");showList(l1);
}
return root;
}

LeetCode 罗马字相关

https://leetcode.com/problems/integer-to-roman/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
char ONE[5] = {' ', 'I', 'X', 'C', 'M'};
char FIVE[4] = {' ', 'V', 'L', 'D'};
char strr[100];
int judgeByte(int num, int * byte) {
int ten = 10, tmp = 1;
while (1) {
if (num / ten == 0) {
*byte = tmp;
return ten;
} else {
ten *= 10;
tmp ++;
}
}
}
void produceRomanSymbol(int num, int ten, int byte, char * result) {
if (byte == 0) {
//sprintf(result + strlen(result), "\n");
return ;
}
int target = num / (ten/10);
//printf("num = %d, ten = %d, target = %d, byte = %d\n", num, ten, target, byte);
if (target == 5) {
sprintf(result + strlen(result), "%c", FIVE[byte]);
}
else if (target > 5) {
if (target == 9) {
sprintf(result + strlen(result), "%c%c", ONE[byte], ONE[byte + 1]);
}
else {
int i = 1;
sprintf(result + strlen(result), "%c", FIVE[byte]);
for (; i <= (target - 5); i ++) {
sprintf(result + strlen(result), "%c", ONE[byte]);
}
}
}
else {
if (target == 4) {
sprintf(result + strlen(result), "%c%c", ONE[byte], FIVE[byte]);
}
else {
int i = 1;
for (; i <= target; i ++) {
sprintf(result + strlen(result), "%c", ONE[byte]);
}
}
}
produceRomanSymbol(num % (ten/10), ten / 10, byte - 1, result);
}
char* intToRoman(int num) {
int byte;
int ten = judgeByte(num, &byte);
memset(strr, 0, 100);
produceRomanSymbol(num, ten, byte, strr);
return strr;
}

https://leetcode.com/problems/roman-to-integer/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int getRomanValue(char ch) {
switch (ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
}
return -1;
}

int romanToInt(char* s) {
int cnt = 0, tmp = 0, i;
for (i = 0; i < strlen(s); i ++) {
// printf("i = %d, ", i);
if (i == 0) {
tmp = getRomanValue(s[i]);
} else {
int diff = getRomanValue(s[i]) - getRomanValue(s[i - 1]);
if (diff == 0) {
tmp += getRomanValue(s[i]);
// printf("** tmp = %d, pos = %c\n", tmp, s[i]);
//cnt += tmp;
} else if(diff > 0) {
cnt += (getRomanValue(s[i]) - tmp);
// printf("cnt = %d, pos = %c\n", cnt, s[i]);
tmp = 0;
} else {
cnt += tmp;
tmp = 0;
tmp += getRomanValue(s[i]);
// printf("tmp = %d, pos = %c\n", tmp, s[i]);
}
}
}
if (tmp != 0)
cnt += tmp;
return cnt;
}

HDOJ 1003 最大子序列和

这道关于DP的经典题终于花时间弄明白了。一定要记录一下啊。题目链接。思路对这个题目,其实最重要的是思路。参考了此博客上面的思路,在这里对DP求解的思路做一些整理。设数组$arr$的长度为$n$,下标从$0$到$n-1$,则最后一个元素表示为$arr[n-1]$。对连续和最大的子数组(后称"
阅读更多

二叉树的(迭代式)遍历

终于到写这篇的时候了。这是一篇对于邓俊辉的《数据结构(C++语言版)(第3版)》中二叉树遍历部分的读书笔记,图片来自DSACPP。感觉这本书很用心。当初要买这本书的原因也就是看到了他对迭代式遍历二叉树的这些内容,很赞。

先序遍历

图05-32.先序遍历过程:先沿左侧通路自顶而下访问沿途节点,再自底而上依次遍历这些节点的右子树
这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/******************************************************************************************
* Data Structures in C++
* ISBN: 7-302-33064-6 & 7-302-33065-3 & 7-302-29652-2 & 7-302-26883-3
* Junhui DENG, [email protected]
* Computer Science & Technology, Tsinghua University
* Copyright (c) 2006-2013. All rights reserved.
******************************************************************************************/

#pragma once

//从当前节点出发,沿左分支不断深入,直至没有左分支的节点;沿途节点遇到后立即访问
template <typename T, typename VST> //元素类型、操作器
static void visitAlongLeftBranch ( BinNodePosi(T) x, VST& visit, Stack<BinNodePosi(T)>& S ) {
while ( x ) {
visit ( x->data ); //访问当前节点
S.push ( x->rc ); //右孩子入栈暂存(可优化:通过判断,避免空的右孩子入栈)
x = x->lc; //沿左分支深入一层
}
}

template <typename T, typename VST> //元素类型、操作器
void travPre_I2 ( BinNodePosi(T) x, VST& visit ) { //二叉树先序遍历算法(迭代版#2)
Stack<BinNodePosi(T)> S; //辅助栈
while ( true ) {
visitAlongLeftBranch ( x, visit, S ); //从当前节点出发,逐批访问
if ( S.empty() ) break; //直到栈空
x = S.pop(); //弹出下一批的起点
}
}
//***************************************************************
template <typename T, typename VST> //元素类型、操作器
void travPre_I1 ( BinNodePosi(T) x, VST& visit ) { //二叉树先序遍历算法(迭代版#1)
Stack<BinNodePosi(T)> S; //辅助栈
if ( x ) S.push ( x ); //根节点入栈
while ( !S.empty() ) { //在栈变空之前反复循环
x = S.pop(); visit ( x->data ); //弹出并访问当前节点,其非空孩子的入栈次序为先右后左
if ( HasRChild ( *x ) ) S.push ( x->rc ); if ( HasLChild ( *x ) ) S.push ( x->lc );
}
}

图05-31.迭代式先序遍历实例(出栈节点以深色示意)
这里写图片描述

中序遍历

图05-33.中序遍历过程:顺着左侧通路,自底而上依次访问沿途各节点及其右子树
这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/******************************************************************************************
* Data Structures in C++
* ISBN: 7-302-33064-6 & 7-302-33065-3 & 7-302-29652-2 & 7-302-26883-3
* Junhui DENG, [email protected]
* Computer Science & Technology, Tsinghua University
* Copyright (c) 2006-2013. All rights reserved.
******************************************************************************************/

#pragma once

template <typename T> //从当前节点出发,沿左分支不断深入,直至没有左分支的节点
static void goAlongLeftBranch ( BinNodePosi(T) x, Stack<BinNodePosi(T)>& S ) {
while ( x ) { S.push ( x ); x = x->lc; } //当前节点入栈后随即向左侧分支深入,迭代直到无左孩子
}

template <typename T, typename VST> //元素类型、操作器
void travIn_I1 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历算法(迭代版#1)
Stack<BinNodePosi(T)> S; //辅助栈
while ( true ) {
goAlongLeftBranch ( x, S ); //从当前节点出发,逐批入栈
if ( S.empty() ) break; //直至所有节点处理完毕
x = S.pop(); visit ( x->data ); //弹出栈顶节点并访问之
x = x->rc; //转向右子树
}
}
// 等价于上面的方法travIn_I1(),只是换了种表达方式
template <typename T, typename VST> //元素类型、操作器
void travIn_I2 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历算法(迭代版#2)
Stack<BinNodePosi(T)> S; //辅助栈
while ( true )
if ( x ) {
S.push ( x ); //根节点进栈
x = x->lc; //深入遍历左子树
} else if ( !S.empty() ) {
x = S.pop(); //尚未访问的最低祖先节点退栈
visit ( x->data ); //访问该祖先节点
x = x->rc; //遍历祖先的右子树
} else
break; //遍历完成
}

图05-34.迭代式中序遍历实例(出栈节点以深色示意)
这里写图片描述

图05-35.中序遍历过程中,在无右孩子的节点处需做回溯
这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/******************************************************************************************
* Data Structures in C++
* ISBN: 7-302-33064-6 & 7-302-33065-3 & 7-302-29652-2 & 7-302-26883-3
* Junhui DENG, [email protected]
* Computer Science & Technology, Tsinghua University
* Copyright (c) 2006-2013. All rights reserved.
******************************************************************************************/

// 寻找此节点中序遍历的后继
template <typename T> BinNodePosi(T) BinNode<T>::succ() { //定位节点v的直接后继
BinNodePosi(T) s = this; //记录后继的临时变量
if ( rc ) { //若有右孩子,则直接后继必在右子树中,具体地就是
s = rc; //右子树中
while ( HasLChild ( *s ) ) s = s->lc; //最靠左(最小)的节点
} else { //否则,直接后继应是“将当前节点包含于其左子树中的最低祖先”,具体地就是
while ( IsRChild ( *s ) ) s = s->parent; //逆向地沿右向分支,不断朝左上方移动
s = s->parent; //最后再朝右上方移动一步,即抵达直接后继(如果存在)
}
return s;
}

template <typename T, typename VST> //元素类型、操作器
void travIn_I3 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历算法(迭代版#3,无需辅助栈)
bool backtrack = false; //前一步是否刚从右子树回溯——省去栈,仅O(1)辅助空间
while ( true )
if ( !backtrack && HasLChild ( *x ) ) //若有左子树且不是刚刚回溯,则
x = x->lc; //深入遍历左子树
else { //否则——无左子树或刚刚回溯(相当于无左子树)
visit ( x->data ); //访问该节点
if ( HasRChild ( *x ) ) { //若其右子树非空,则
x = x->rc; //深入右子树继续遍历
backtrack = false; //并关闭回溯标志
} else { //若右子树空,则
if ( ! ( x = x->succ() ) ) break; //回溯(含抵达末节点时的退出返回)
backtrack = true; //并设置回溯标志
}
}
}

template <typename T, typename VST> //元素类型、操作器
void travIn_I4 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历(迭代版#4,无需栈或标志位)
while ( true )
if ( HasLChild ( *x ) ) //若有左子树,则
x = x->lc; //深入遍历左子树
else { //否则
visit ( x->data ); //访问当前节点,并
while ( !HasRChild ( *x ) ) //不断地在无右分支处
if ( ! ( x = x->succ() ) ) return; //回溯至直接后继(在没有后继的末节点处,直接退出)
else visit ( x->data ); //访问新的当前节点
x = x->rc; //(直至有右分支处)转向非空的右子树
}
}

后序遍历

图05-36.后序遍历过程也可划分为模式雷同的若干段
这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/******************************************************************************************
* Data Structures in C++
* ISBN: 7-302-33064-6 & 7-302-33065-3 & 7-302-29652-2 & 7-302-26883-3
* Junhui DENG, [email protected]
* Computer Science & Technology, Tsinghua University
* Copyright (c) 2006-2013. All rights reserved.
******************************************************************************************/

#pragma once

template <typename T> //在以S栈顶节点为根的子树中,找到最高左侧可见叶节点
static void gotoHLVFL ( Stack<BinNodePosi(T)>& S ) { //沿途所遇节点依次入栈
while ( BinNodePosi(T) x = S.top() ) //自顶而下,反复检查当前节点(即栈顶)
if ( HasLChild ( *x ) ) { //尽可能向左
if ( HasRChild ( *x ) ) S.push ( x->rc ); //若有右孩子,优先入栈
S.push ( x->lc ); //然后才转至左孩子
} else //实不得已
S.push ( x->rc ); //才向右
S.pop(); //返回之前,弹出栈顶的空节点
}

template <typename T, typename VST>
void travPost_I ( BinNodePosi(T) x, VST& visit ) { //二叉树的后序遍历(迭代版)
Stack<BinNodePosi(T)> S; //辅助栈
if ( x ) S.push ( x ); //根节点入栈
while ( !S.empty() ) {
if ( S.top() != x->parent ) //若栈顶非当前节点之父(则必为其右兄),此时需
gotoHLVFL ( S ); //在以其右兄为根之子树中,找到HLVFL(相当于递归深入其中)
x = S.pop(); visit ( x->data ); //弹出栈顶(即前一节点之后继),并访问之
}
}

图05-37.迭代式后序遍历实例(出栈节点以深色示意,发生gotoHLVFL()调用的节点以大写字母示意)
这里写图片描述

常用排序算法

慢慢再温习一遍之前学习过的排序算法吧,一点点地积累。

插入排序

插入排序的基本思想是在遍历数组的过程中,假设在序号 i (i>=1)之前的元素即 [0..i-1] 都已经排好序,本趟需要找到 i 对应的元素 x 的正确位置 k ,并且在寻找这个位置 k 的过程中逐个将比较过的元素往后移一位,为元素 x “腾位置”,最后将 k 对应的元素值赋为 x ,一般情况下,插入排序的时间复杂度和空间复杂度分别为O(n2) 和 O(1)。(通俗说法:把数组后面那些没排序的元素换到数组前面已经排好序的部分里对应的位置)

例如:45 80 48 40 22 78
第一轮:45 80 48 40 22 78 —> 45 80 48 40 22 78 i=1
第二轮:45 80 48 40 22 78 —> 45 48 80 40 22 78 i=2
第三轮:45 48 80 40 22 78 —> 40 45 48 80 22 78 i=3
第四轮:40 45 48 80 22 78 —> 22 40 45 48 80 78 i=4
第五轮:22 40 45 48 80 78 —> 22 40 45 48 78 80 i=5
(红色代表此轮要插入的元素,红色左边是已经排好序的,右边是待排序的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @param int[] 未排序数组
* @return int[] 排完序数组
*/
public static int[] InsertSort(int[] array){
for(int i =1;i<array.length;i++){
int temp = array[i];
int j = i-1;
while(j>=0 && temp < array[j] ){
array[j+1] = array[j];
j--;
}
array[j+1] = temp;
}
return array;
}

选择排序

选择排序的基本思想是遍历数组的过程中,以 i 代表当前需要排序的序号,则需要在剩余的 [i+1,…n-1] 中找出其中的最小值,然后将找到的最小值与 i 指向的值进行交换。因为每一趟确定元素的过程中都会有一个选择最大值/最小值的子流程,所以人们形象地称之为选择排序。选择排序的时间复杂度和空间复杂度分别为O(n2)和O(1)。(通俗说法:每次把剩余数组里最小的选出来放在数组的前面。所以第一次选出来的就是数组里面最小的,第二次选出来的就是数组里面第二小的,依次。。。。。)

例如:45 80 48 40 22 78
第一轮:45 80 48 40 22 78 —> 22 80 48 40 45 78 i=0
第二轮:22 80 48 40 45 78 —> 22 40 48 80 45 78 i=1
第三轮:22 40 48 80 45 78 —> 22 40 45 80 48 78 i=2
第四轮:22 40 45 80 48 78 —> 22 40 45 48 80 78 i=3
第五轮:22 40 45 48 80 78 —> 22 40 45 48 78 80 i=4
(红色代表此轮需要排序的序号的元素,红色左边是已经排好序的,右边是待排序的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @param int[] 未排序数组
* @return int[] 排完序数组
*/
public int[] sortSelect(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
int miniPost = i;
for (int m = i + 1; m < arr.length; m++) {
if (arr[m] < arr[miniPost])
  miniPost = m;
}
if (arr[i] > arr[miniPost]) {
int temp = arr[i];
arr[i] = arr[miniPost];
arr[miniPost] = temp;
}
}
return arr;
}

归并排序

来自:https://www.cnblogs.com/chengxiao/p/6194356.html,图非常简洁明了。

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。

分而治之

这里写图片描述
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

合并相邻有序子序列

再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
这里写图片描述
这里写图片描述

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package sortdemo;

import java.util.Arrays;

/**
* Created by chengxiao on 2016/12/8.
*/
public class MergeSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
}

/**
执行结果
[1, 2, 3, 4, 5, 6, 7, 8, 9]
**/

归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

快速排序

大概的思想是(假设为升序),先以一个数为参照,然后将所有的大于它的数移到它的后面,小于它的数移到前面,再以此数的位置,将数组分成两部分,再对这两部分做相同的操作…如此递归,直至完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) {
int[] arr = new int[]{6,2,4,99,22,56,11,5,7,4,1};
QuickSort(arr, 0, arr.length - 1);
printArray(arr);
}

static void QuickSort(int[] arr, int left, int right){
if (left > right)
return;
int pivot_index = partition1(arr, left, right);
QuickSort(arr, left, pivot_index - 1);
QuickSort(arr, pivot_index + 1, right);
}

static int partition1(int[] arr, int left, int right){
int pivot = arr[left];
while(left < right){
while(left < right && arr[right] >= pivot){
right--;
}
arr[left] = arr[right];

while(left < right && arr[left] <= pivot){
left++;
}
arr[right] = arr[left];

}
arr[left] = pivot;
return left;
}