# 数组、链表、栈和队列相关的题目

# 数组

# 合并两个有序数组 LeetCode 88

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。

示例 3
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

解 1

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int[] arr = new int[m + n];
        int j = 0;
        int k = 0;

        // nums1 没有数据
        if (m == 0) {
            for (int i = 0; i < nums1.length; i++) {
                nums1[i] = nums2[i];
            }
        } else if (n == 0) {    // nums2 没有数据
        } else {                // nums1 和 nums2 都有数据
            for (int i = 0; i < arr.length; i++) {
                // nums2 已经排完
                if (k >= n) {
                    arr[i] = nums1[j];
                    j++;
                } else if (j < m && nums1[j] < nums2[k]) { // 都没排好 或 nums1 已经排完
                    arr[i] = nums1[j];  // nums1 当前小
                    j++;
                } else {
                    arr[i] = nums2[k];  // nums2 当前小
                    k++;
                }
            }
            for (int i = 0; i < arr.length; i++) {
                nums1[i] = arr[i];
            }
        }
    }
}
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

解 2

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1;
        int j = n - 1;
        for (int k = m + n - 1; k >= 0; k--) {
            if (j < 0 || (i >= 0 && nums1[i] >= nums2[j])) {  // nums2 已经排完 或 i > 0 时可以继续放 nums1(防止越界)
                nums1[k] = nums1[i];
                i--;
            } else {
                nums1[k] = nums2[j];
                j--;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 删除有序数组中的重复项 LeetCode 26

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

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

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

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

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

示例 1
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

解 1

public int removeDuplicates(int[] nums) {
    int index = 0;
    // 双指针判断

    // 外层循环固定指针 i
    out:
    for (int i = 0; i < nums.length; i++) {
        // 内层循环固定指针 j
        in:
        for (int j = i + 1; j <nums.length; j++) {  // 对于每一个 i, 从当前 i 的下一个开始和它比
            if (nums [i] >= nums[j]) {          // 如果当前 i >= 当前 j,表示当前 i 是经过覆盖的
                if (j == nums.length - 1) { // j 遍历过剩下所有元素,都没有发现需要替换,说明已经寻找完毕
                    break out;          // 退出外层循环
                } else {                    // j 还没有遍历完毕剩下所有元素,继续遍历
                    continue;
                }
            } else if (nums[i] < nums[j]) {    // 当前 i < 当前 j,说明找到了需要替换的元素
                nums[i + 1] = nums [j]; // 将 j 覆盖当前 i 的下一个元素
                index++;                // 有序无重复序列 +1
                continue out;           // 继续外层循环,即与当前 i 相同所有元素已经判断完毕,i 进下一位
            }
        }
    }
    return index + 1;   // 输出循环是 <, 所以手动 + 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

解 2

public int removeDuplicates(int[] nums) {
    int n = 0;
    for (int i = 0; i < nums.length; i++) {
        if (i == 0 || nums[i] != nums [i - 1]) {  // 只要和前一个不一样 或者是第一个元素,覆盖
            nums[n] = nums[i];  // 覆盖到有序部分后面
            n++; // 有序部分 +1
        }
    }
    return n;
}
1
2
3
4
5
6
7
8
9
10

# 移动零 LeetCode 283

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。

示例
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

解 1

public void moveZeroes(int[] nums) {
    int temp = -1;
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < nums.length; j++) {
            if (nums[j] == 0 && j + 1 < nums.length) {
                temp = nums[j + 1];
                nums[j] = temp;
                nums[j + 1] = 0;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

解 2

public void moveZeroes(int[] nums) {
    int n = 0;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != 0) { // 非 0 项需要,覆盖到前非 0 段
            nums[n] = nums[i];
            n++;
        }
    }
    // 补 0
    while (n < nums.length) {
        nums[n] = 0;
        n++;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 链表

# 反转链表 LeetCode 206

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2
输入:head = [1,2]
输出:[2,1]

示例 3
输入:head = []
输出:[]

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode last = null;

        while (head != null) {
            ListNode nextHead = head.next;  // 记录下一个结点

            head.next = last;   // 将当前结点指向上一个结点
            
            last = head;        // 将当前结点标记为 last
            head = nextHead;    // 继续处理下一个结点
        }

        return last;
    }
}
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

也可以理解为将当前头拆下,赋值给新链表 last, 然后将新链表的头指定为当前结点。即头删旧链表;头插新链表

# K 个一组翻转链表 LeetCode 25

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

  • 利用常数额外空间
  • 使用实际的结点交换而不是内部值变化

示例 1
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

示例 3
输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]

示例 4
输入:head = [1], k = 1
输出:[1]

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 建立保护结点防止为空
        ListNode protect = new ListNode(0, head);

        // 找到每组的开头和结尾,按组遍历反转
        ListNode last = protect;   // 上一组的结尾
        while (head != null) {
            ListNode end = getEnd(head, k);     // 获取本组的结尾位置
            
            // 边界处理
            if (end == null) {                  // 到结尾不够一组
                break;
            }

            // 反转链表
            ListNode nextGroupHead = end.next;  // 保存下一组的头,后续反转后会被修改防止丢失
            reverseList(head, end);             // 处理 head 到 end 中间的反转
            last.next = end;                    // 上一组的 next 指向新头(前 end)
            head.next = nextGroupHead;          // 本组的新尾(前 head)指向下一组的头

            // 分组遍历
            last = head;                        // 将当前标记为 last
            head = nextGroupHead;               // 继续处理下一组
        }

        // 返回结果
        return protect.next;
    }

    // 走 K 步后的位置(每组的结尾)
    public ListNode getEnd(ListNode head, int k) {
        while (head != null) {
            k--;
            if (k == 0) {
                break;
            }
            head = head.next;
        }
        return head;
    }

    // 反转链表
    public void reverseList(ListNode head, ListNode end) {
        // 当步长为 1
        if (head == end) {
            return;
        }

        ListNode last = head;   // 从 head 开始反转,直接将 last 标记为当前 head
        head = head.next;       // 只修改 n-1 个链接

        while (head != end) {
            ListNode nextHead = head.next;

            head.next = last;
            
            last = head;
            head = nextHead;
        }

        // 手动修改本组结尾的指向
        end.next = last;
    }

}
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

# 环形链表 LeetCode 141

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

  • 使用常量空间复杂度

示例 1
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        // 快慢指针
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;  // 快指针每次走两步
            head = head.next;       // 慢指针每次走一步
            if (fast == head) {     // 如果快指针有机会追上满指针说明有环
                return true;
            }
        }
        return false;               // 如果快指针先遍历完且未与慢指针相遇说明无环
    }
}
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

# 环形链表 II LeetCode 142

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

  • 不允许修改给定的链表。
  • 使用常量空间复杂度

示例 1
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

[分析]
设置快慢指针,快指针每次走两步,慢指针每次走一步
假定从头结点开始到入环结点距离为 l
从入环结点到环内相遇结点距离为 p
环长为 r
在环内,相遇点到入环点距离为 r-p 当两指针相遇时,有 2(l+p) = l+p+kr,其中 k 为绕环趟数
即慢指针走到相遇点共计 l+p 个结点,快指针共计 l+p+kr 个结点。因为快指针每次走两步,所以当慢指针走两倍时,两个指针走过的结点总数相等。
化简有 l = kr-p
提出一个 r 有 l = (k-1)r+r-p
理解为从头结点到入环结点的距离,等同于从相遇点到入环点的距离加上 k-1 圈环内循环。即 l 与 r-p 的关系是 k-1 倍
那么如果此时从头结点和相遇点分别设置一个慢指针,每次同时移动一次,它们必定相遇在入环处

# 栈和队列

# 有效的括号 LeetCode 20

class Solution {
    public boolean isValid(String s) {
        Stack stack = new Stack();
        for (char c : s.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            } else {
                if (stack.empty()) {
                    return false;
                }

                char tmp = (char) stack.pop();
                switch (c) {
                    case ')':
                        if (tmp != '(') {
                            return false;
                        }
                        break;
                    case ']':
                        if (tmp != '[') {
                            return false;
                        }
                        break;
                    case '}':
                        if (tmp != '{') {
                            return false;
                        }
                        break;
                }
            } 
        }

        return stack.empty();
    }
}
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

# 逆波兰表达式求值 LeetCode 150

class Solution {
    public int evalRPN(String[] tokens) {
        Stack stack = new Stack();
        for (String token : tokens) {
            if (token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {
                int optB = (int) stack.pop();
                int optA = (int) stack.pop();
                stack.push(calc(optA, optB, token));
            } else {
                stack.push(Integer.parseInt(token));
            }
        }
        return (int) stack.pop();
    }

    public int calc (int optA, int optB, String token) {
        if (token.equals("+")) return optA + optB;
        if (token.equals("-")) return optA - optB;
        if (token.equals("*")) return optA * optB;
        if (token.equals("/")) return optA / optB;
        return 0;
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 基本计算器 LeetCode 224

class Solution {
    public int calculate(String s) {
        Stack opts = new Stack();
        List<String> tokens = new ArrayList<String>();
        boolean numStarted = false;
        boolean needZero = true;
        // 将字符串转为 Int
        int value = 0;
        for (char c : s.toCharArray()) {
            if (Character.isDigit(c)) {
                value = value * 10 + c - '0';
                numStarted = true;
                continue;
            } else if (numStarted) {
                tokens.add(String.valueOf(value));
                numStarted = false;
                needZero = false;
                value = 0;
            }
            if (c == ' ') continue;

            // 处理运算符
            if (c == '(') {
                opts.push(c);
                needZero = true;
                continue;
            }
            if (c == ')') {
                while ((char)opts.peek() != '(') {
                    tokens.add(String.valueOf((char)opts.pop()));
                }
                opts.pop();
                needZero = false;
                continue;
            }

            // 处理 +-*/
            if (needZero) {
                tokens.add("0");
            }
            while (!opts.empty() && getRank((char) opts.peek()) >= getRank(c)) {
                tokens.add(String.valueOf((char) opts.pop()));
            }
            opts.push(c);
            needZero = true;
        }
        if (numStarted) {
            tokens.add(String.valueOf(value));
        }
        while (!opts.empty()) {
            tokens.add(String.valueOf((char) opts.pop()));
        }
        return evalRPN(tokens.toArray(new String[tokens.size()]));
    }

    public int getRank(char c) {
        if (c == '+' || c == '-') {
            return 1;
        }
        if (c == '*' || c == '/') {
            return 2;
        }
        return 0;
    }

    public int evalRPN(String[] tokens) {
        Stack stack = new Stack();
        for (String token : tokens) {
            if (token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {
                int optB = (int) stack.pop();
                int optA = (int) stack.pop();
                stack.push(calc(optA, optB, token));
            } else {    // 是数字
                stack.push(Integer.parseInt(token));
            }
        }
        return (int) stack.pop();
    }

    public int calc(int optA, int optB, String token) {
        if (token.equals("+")) return optA + optB;
        if (token.equals("-")) return optA - optB;
        if (token.equals("*")) return optA * optB;
        if (token.equals("/")) return optA / optB;
        return 0;
    }
}
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