26 remove-duplicates-from-sorted-array

26. 删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

1
2
3
4
5
给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

1
2
3
4
5
给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

1
2
3
4
5
6
7
8
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

双指针法

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int i = 0;
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
}

复杂度分析

  • 时间复杂度:O(n),假设数组的长度是 n,那么 i 和 j 分别最多遍历 n 步。

  • 空间复杂度:O(1)。

440. 字典序的第K小数字

440. 字典序的第K小数字

题目

给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字。

注意:1 ≤ k ≤ n ≤ 109。

示例 :

1
2
3
4
5
6
7
8
输入:
n: 13 k: 2

输出:
10

解释:
字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。

题解

乍一看这一题貌似毫无头绪,什么是字典序?如何定位这个数?没错,刚接触这个题目的时候,我的脑筋里也是一团乱麻。

但是我觉得作为一个拥有聪明才智的程序员来说,最重要的能力就是迅速抽象问题、拆解问题的能力。经过一段时间的思考,我的脑筋里还是没有答案。

哈哈。

言归正传,我们来分析一下这个问题。

首先,什么是字典序?

什么是字典序?
简而言之,就是根据数字的前缀进行排序,

比如10 < 9,因为10的前缀是1,比9小,

再比如112 < 12,因为112的前缀11小于12。

这样排序下来,会跟平常的升序排序会有非常大的不同。先给你一个直观的感受,一个数乘10,或者加1,哪个大?可能你会吃惊,后者会更大。

但其实掌握它的本质之后,你一点都不会吃惊。

问题建模
画一个图你就懂了。

image.png

每一个节点都拥有10个孩子节点,因为作为一个前缀 ,它后面可以接0~9这十个数字。而且你可以非常容易地发现,整个字典序排列也就是对十叉树进行层序遍历。1, 10, 11, 12, 13 … 100, 101, …

回到题目的意思,我们需要找到排在第k位的数。找到他的排位,需要搞清楚三件事情:

怎么确定一个前缀下所有子节点的个数?
如果第k个数在当前的前缀下,怎么继续往下面的子节点找?
如果第k个数不在当前的前缀,即当前的前缀比较小,如何扩大前缀,增大寻找的范围?
接下来 ,我们一一拆解这些问题。

理顺思路

  1. 确定指定前缀下所有子节点数
    现在的任务就是给定一个前缀,返回下面子节点总数。

我们现在的思路就是用下一个前缀的起点减去当前前缀的起点,那么就是当前前缀下的所有子节点数总和啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//prefix是前缀,n是上界
var getCount = (prefix, n) => {
let cur = prefix;
let next = prefix + 1;//下一个前缀
let count = 0;
//当前的前缀当然不能大于上界
while(cur <= n) {
count += next - cur;//下一个前缀的起点减去当前前缀的起点
cur *= 10;
next *= 10;
// 如果说刚刚prefix是1,next是2,那么现在分别变成10和20
// 1为前缀的子节点增加10个,十叉树增加一层, 变成了两层

// 如果说现在prefix是10,next是20,那么现在分别变成100和200,
// 1为前缀的子节点增加100个,十叉树又增加了一层,变成了三层
}
return count;//把当前前缀下的子节点和返回去。
}

当然,不知道大家发现一个问题没有,当next的值大于上界的时候,那以这个前缀为根节点的十叉树就不是满十叉树了啊,应该到上界那里,后面都不再有子节点。因此,count += next - cur还是有些问题的,我们来修正这个问题:

1
count += Math.max(n+1, next) - cur;

你可能会问:咦?怎么是n+1,而不是n呢?不是说好了n是上界吗?

我举个例子,假若现在上界n为12,算出以1为前缀的子节点数,首先1本身是一个节点,接下来要算下面10,11,12,一共有4个子节点。

那么如果用Math.max(n, next) - cur会怎么样?

这时候算出来会少一个,12 - 10加上根节点,最后只有3个。因此我们务必要写n+1。

现在,我们搞定了前缀的子节点数问题。

  1. 第k个数在当前前缀下
    现在无非就是往子树里面去看。

prefix这样处理就可以了。

1
prefix *= 10

3.第k个数不在当前前缀下
说白了,当前的前缀小了嘛,我们扩大前缀。

1
prefix ++;

框架搭建
整合一下刚刚的思路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let findKthNumber = function(n, k) {
let p = 1;//作为一个指针,指向当前所在位置,当p==k时,也就是到了排位第k的数
let prefix = 1;//前缀
while(p < k) {
let count = getNumber(prefix, n);//获得当前前缀下所有子节点的和
if(p + count > k) { //第k个数在当前前缀下
prefix *= 10;
p++; //把指针指向了第一个子节点的位置,比如11乘10后变成110,指针从11指向了110
} else if(p + count <= k) { //第k个数不在当前前缀下
prefix ++;
p += count;//注意这里的操作,把指针指向了下一前缀的起点
}
}
return prefix;
};

完整代码展示

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
/**
* @param {number} n
* @param {number} k
* @return {number}
*/
var findKthNumber = function(n, k) {
let getCount = (prefix, n) => {
let count = 0;
for(let cur = prefix, next = prefix + 1; cur <= n; cur *= 10, next *= 10)
count += Math.min(next, n+1) - cur;
return count;
}
let p = 1;
let prefix = 1;
while(p < k) {
let count = getCount(prefix, n);
if(p + count > k) {
prefix *= 10;
p++;
} else if(p + count <= k) {
prefix ++;
p += count;
}
}
return prefix;
};
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
public class Solution {
/**
* @param n: a integer
* @param k: a integer
* @return: return a integer
*/
public int findKthNumber(int n, int k) {
int curr = 1;
k = k - 1;
while (k > 0) {
int steps = calSteps(n, curr, curr + 1);
if (steps <= k) { //如果不在当前层,减去steps
curr += 1;
k -= steps;
}
else { //说明在当前层,curr*10缩小搜索范围继续查找
curr *= 10;
k -= 1;
}
}
return curr;
}
//use long in case of overflow
public int calSteps(int n, long n1, long n2) { //计算curr开头和curr+1开头之间的字符串数量
int steps = 0;
while (n1 <= n) {
steps += Math.min(n + 1, n2) - n1; //每次加上当前的字符串数量
n1 *= 10; //每次均扩大10倍
n2 *= 10;
}
return steps;
}
}

54. 螺旋矩阵

image.png

54. 螺旋矩阵

给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。

示例 1:

1
2
3
4
5
6
7
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]

示例 2:

1
2
3
4
5
6
7
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]

方法 1:模拟

直觉

绘制螺旋轨迹路径,我们发现当路径超出界限或者进入之前访问过的单元格时,会顺时针旋转方向。

算法

假设数组有 R 行 C 列,seen[r][c] 表示第 r 行第 c 列的单元格之前已经被访问过了。当前所在位置为 (r, c),前进方向是 di。我们希望访问所有 R x C 个单元格。

当我们遍历整个矩阵,下一步候选移动位置是 (cr, cc)。如果这个候选位置在矩阵范围内并且没有被访问过,那么它将会变成下一步移动的位置;否则,我们将前进方向顺时针旋转之后再计算下一步的移动位置。

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 {
public List<Integer> spiralOrder(int[][] matrix) {
List ans = new ArrayList();
if (matrix.length == 0) return ans;
int R = matrix.length, C = matrix[0].length;
boolean[][] seen = new boolean[R][C];
int[] dr = {0, 1, 0, -1};
int[] dc = {1, 0, -1, 0};
int r = 0, c = 0, di = 0;
for (int i = 0; i < R * C; i++) {
ans.add(matrix[r][c]);
seen[r][c] = true;
int cr = r + dr[di];
int cc = c + dc[di];
if (0 <= cr && cr < R && 0 <= cc && cc < C && !seen[cr][cc]){
r = cr;
c = cc;
} else {
di = (di + 1) % 4;
r += dr[di];
c += dc[di];
}
}
return ans;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution(object):
def spiralOrder(self, matrix):
if not matrix: return []
R, C = len(matrix), len(matrix[0])
seen = [[False] * C for _ in matrix]
ans = []
dr = [0, 1, 0, -1]
dc = [1, 0, -1, 0]
r = c = di = 0
for _ in range(R * C):
ans.append(matrix[r][c])
seen[r][c] = True
cr, cc = r + dr[di], c + dc[di]
if 0 <= cr < R and 0 <= cc < C and not seen[cr][cc]:
r, c = cr, cc
else:
di = (di + 1) % 4
r, c = r + dr[di], c + dc[di]
return ans

复杂度分析

  • 时间复杂度: O(N),其中 N 是输入矩阵所有元素的个数。因为我们将矩阵中的每个元素都添加进答案里。
  • 空间复杂度: O(N),需要两个矩阵 seen 和 ans 存储所需信息。

方法 2:按层模拟

直觉

答案是最外层所有元素按照顺时针顺序输出,其次是次外层,以此类推。

算法

我们定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。例如,下图矩阵最外层元素都是第 1 层,次外层元素都是第 2 层,然后是第 3 层的。

1
2
3
4
5
[[1, 1, 1, 1, 1, 1, 1],
[1, 2, 2, 2, 2, 2, 1],
[1, 2, 3, 3, 3, 2, 1],
[1, 2, 2, 2, 2, 2, 1],
[1, 1, 1, 1, 1, 1, 1]]

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public List < Integer > spiralOrder(int[][] matrix) {
List ans = new ArrayList();
if (matrix.length == 0)
return ans;
int r1 = 0, r2 = matrix.length - 1;
int c1 = 0, c2 = matrix[0].length - 1;
while (r1 <= r2 && c1 <= c2) {
for (int c = c1; c <= c2; c++) ans.add(matrix[r1][c]);
for (int r = r1 + 1; r <= r2; r++) ans.add(matrix[r][c2]);
if (r1 < r2 && c1 < c2) {
for (int c = c2 - 1; c > c1; c--) ans.add(matrix[r2][c]);
for (int r = r2; r > r1; r--) ans.add(matrix[r][c1]);
}
r1++;
r2--;
c1++;
c2--;
}
return ans;
}
}

复杂度分析

  • 时间复杂度: O(N),其中 N 是输入矩阵所有元素的个数。因为我们将矩阵中的每个元素都添加进答案里。
  • 空间复杂度: O(N),需要矩阵 ans 存储信息。

方法 3:顺时针旋转

这里的方法不需要记录已经走过的路径,所以执行用时和内存消耗都相对较小

  1. 首先设定上下左右边界
  2. 其次向右移动到最右,此时第一行因为已经使用过了,可以将其从图中删去,体现在代码中就是重新定义上边界
  3. 判断若重新定义后,上下边界交错,表明螺旋矩阵遍历结束,跳出循环,返回答案
  4. 若上下边界不交错,则遍历还未结束,接着向下向左向上移动,操作过程与第一,二步同理
  5. 不断循环以上步骤,直到某两条边界交错,跳出循环,返回答案
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
import java.util.*;

public class Main {

public static void main(String[] args) {
// Scanner in = new Scanner(System.in);
// int n = in.nextInt();
// in.close();
int[][] matrix = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
List<Integer> list = spiralOrder(matrix);
System.out.println(list);
}

static List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
if (matrix.length == 0)
return list;
int u = 0, d = matrix.length - 1, l = 0, r = matrix[0].length - 1;
while (true) {
// 向右
for (int i = l; i <= r; i++)
list.add(matrix[u][i]);
if (++u > d)
break;
// 向下
for (int i = u; i <= d; i++)
list.add(matrix[i][r]);
if (--r < l)
break;
// 向左
for (int i = r; i >= l; i--)
list.add(matrix[d][i]);
if (--d < u)
break;
// 向上
for (int i = d; i >= u; i--)
list.add(matrix[i][l]);
if (++l > r)
break;
}
return list;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector <int> ans;
if(matrix.empty()) return ans; //若数组为空,直接返回答案
int u = 0; //赋值上下左右边界
int d = matrix.size() - 1;
int l = 0;
int r = matrix[0].size() - 1;
while(true)
{
for(int i = l; i <= r; ++i) ans.push_back(matrix[u][i]); //向右移动直到最右
if(++ u > d) break; //重新设定上边界,若上边界大于下边界,则遍历遍历完成,下同
for(int i = u; i <= d; ++i) ans.push_back(matrix[i][r]); //向下
if(-- r < l) break; //重新设定有边界
for(int i = r; i >= l; --i) ans.push_back(matrix[d][i]); //向左
if(-- d < u) break; //重新设定下边界
for(int i = d; i >= u; --i) ans.push_back(matrix[i][l]); //向上
if(++ l > r) break; //重新设定左边界
}
return ans;
}
};

118. 杨辉三角

杨辉三角

题目

给定一个非负整数 numRows,生成杨辉三角的前 numRows行。

杨辉三角

在杨辉三角中,每个数是它左上方和右上方的数的和。

示例:

1
2
3
4
5
6
7
8
9
输入: 5
输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]

题解

  1. 动态规划

思路

如果能够知道一行杨辉三角,我们就可以根据每对相邻的值轻松地计算出它的下一行。

算法

虽然这一算法非常简单,但用于构造杨辉三角的迭代方法可以归类为动态规划,因为我们需要基于前一行来构造每一行。

首先,我们会生成整个 triangle 列表,三角形的每一行都以子列表的形式存储。然后,我们会检查行数为 0 的特殊情况,否则我们会返回 [1]。如果 numRows > 0,那么我们用 [1] 作为第一行来初始化 triangle with [1],并按如下方式继续填充:

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
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> triangle = new ArrayList<List<Integer>>();

// First base case; if user requests zero rows, they get zero rows.
if (numRows == 0) {
return triangle;
}

// Second base case; first row is always [1].
triangle.add(new ArrayList<>());
triangle.get(0).add(1);

for (int rowNum = 1; rowNum < numRows; rowNum++) {
List<Integer> row = new ArrayList<>();
List<Integer> prevRow = triangle.get(rowNum-1);

// The first row element is always 1.
row.add(1);

// Each triangle element (other than the first and last of each row)
// is equal to the sum of the elements above-and-to-the-left and
// above-and-to-the-right.
for (int j = 1; j < rowNum; j++) {
row.add(prevRow.get(j-1) + prevRow.get(j));
}

// The last row element is always 1.
row.add(1);

triangle.add(row);
}

return triangle;
}
}

复杂度分析

  • 时间复杂度: O(numRows²)

虽然更新 triangle 中的每个值都是在常量时间内发生的,
但它会被执行 O(numRows²) 次。想要了解原因,就需要考虑总共有多少次循环迭代。很明显外层循环需要运行 numRows 次,但在外层循环的每次迭代中,内层循环要运行 rowNumrowNum 次。因此,triangle 发生的更新总数为
1 + 2 + 3 + ... + *numRows*,根据高斯公式有

image.png

  • 空间复杂度:O(numRows²)

因为我们需要存储我们在 triangle 中更新的每个数字,
所以空间需求与时间复杂度相同。

  1. Python解法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def generate(self, num_rows):
triangle = []

for row_num in range(num_rows):
# The first and last row elements are always 1.
row = [None for _ in range(row_num+1)]
row[0], row[-1] = 1, 1

# Each triangle element is equal to the sum of the elements
# above-and-to-the-left and above-and-to-the-right.
for j in range(1, len(row)-1):
row[j] = triangle[row_num-1][j-1] + triangle[row_num-1][j]

triangle.append(row)

return triangle
  1. 递归解法

通过numRows-1,求numRows行,递归求解

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>> listw = new ArrayList<List<Integer>>();

public List<List<Integer>> generate(int numRows) {
List<Integer> listn = new ArrayList<Integer>();
if (numRows == 0)
return listw;
generate(numRows - 1);
for (int i = 0; i < numRows; i++) {
if (i == 0) {
listn.add(1);
continue;
} else if (i == numRows - 1) {
listn.add(1);
continue;
} else if (i == numRows - 2) {
listn.add(listw.get(numRows - 2).get(i - 1) + 1);
continue;
} else {
listn.add(listw.get(numRows - 2).get(i - 1) + listw.get(numRows - 2).get(i));
}
}
listw.add(listn);
return listw;
}
}

24. 两两交换链表中的节点

image.png

24. 两两交换链表中的节点

题目

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

题解

  1. 非递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode temp = pre;
while(temp.next != null && temp.next.next != null) {
ListNode start = temp.next;
ListNode end = temp.next.next;
temp.next = end;
start.next = end.next;
end.next = start;
temp = start;
}
return pre.next;
}
}
  1. 递归解法

    1. 详细介绍一下递归的思路;
    2. 递归和栈处理问题类似,先把问题从前往后收集起来,然后再从后往前处理每一个问题;
    3. 两两交换链表结点,先处理最后两个或一个节点,然后再从后往前处理每一对节点;
    4. 先创建一个next临时结点保存head的下一个结点,然后让head指向下下一个节点,最后让 next节点指向head结点;
    5. 此题只有处理完后面的结点才可处理前面的结点,画图更容易理解;

image.png

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null)
return head;

// 三个节点顺序:head, next, swapPairs(next.next)
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
}

334. 反转字符串

image.png

334. 反转字符串

题目

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

示例 1:

输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]    

题解

  1. 递归解法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public void reverseString(char[] s) {
swap(0, s.length-1, s);
}

public void swap(int start, int end, char[] s) {
if(start >= end){
return;
}
char temp = s[start];
s[start] = s[end];
s[end] = temp;
swap(start+1, end-1, s);
}
}
  1. 循环解法
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public void reverseString(char[] s) {
int j=s.length-1;
for(int i=0;i<s.length/2;i++){
char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
j--;
}
}
}
  1. Python解法
1
2
def reverseString(self, s: List[str]) -> None:
s[0::]=s[::-1]
  1. c++解法

双指针,交换头尾两个指针所指的两个位置的值,指针向中间移动一个位置,重复以上操作,直到两个指针交错;

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
void reverseString(vector<char>& s) {
int i = 0;
int j = s.size() - 1;
while(i<j)
{
swap(s[i],s[j]);
++ i;
-- j;
}
}
};
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×