刷题日记 - 1
最近更新:2025-10-20   |   字数总计:2.3k   |   阅读估时:10分钟   |   阅读量:
  1. 刷题日记 - 1
    1. 88. 合并两个有序数组 - 力扣(LeetCode)
    2. 21. 合并两个有序链表 - 力扣(LeetCode)
    3. 23. 合并 K 个升序链表 - 力扣(LeetCode)
    4. 27. 移除元素 - 力扣(LeetCode)
    5. 26. 删除有序数组中的重复项 - 力扣(LeetCode)
    6. 80. 删除有序数组中的重复项 II - 力扣(LeetCode)

刷题日记 - 1

记录刷题。

88. 合并两个有序数组 - 力扣(LeetCode)

难度:简单

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 void merge(int[] nums1, int m, int[] nums2, int n) {
int i = 0; // nums1 的索引
int j = 0; // nums2 的索引
int[] copy = new int[m]; // 创建一个数组来存储 nums1 的前 m 个元素的副本
System.arraycopy(nums1, 0, copy, 0, m); // 复制 nums1 的前 m 个元素到 copy 中

int num1; // 存储 copy[i] 的值
int num2; // 存储 nums2[j] 的值
int idx = 0; // 合并后的数组索引

// 合并两个数组
while (i < m && j < n) {
num1 = copy[i];
num2 = nums2[j];
if (num1 <= num2) {
nums1[idx] = num1; // 如果 num1 小于等于 num2,将 num1 放入 nums1
i++;
} else {
nums1[idx] = num2; // 否则,将 num2 放入 nums1
j++;
}
idx++;
}

// 如果 copy 还有剩余的元素,添加到 nums1 中
while (i < m) {
nums1[idx++] = copy[i++];
}

// 如果 nums2 还有剩余的元素,添加到 nums1 中
while (j < n) {
nums1[idx++] = nums2[j++];
}
}
}

先将nums1数组复制到额外空间中,便于访问。

然后利用两个指针和一个idx,和合并有序链表的思想类似。

21. 合并两个有序链表 - 力扣(LeetCode)

难度:简单

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
/**
* 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 mergeTwoLists(ListNode list1, ListNode list2) {
// 创建一个虚拟头节点 dummy,用于简化边界条件的处理
ListNode dummy = new ListNode(-1);
// 指针 p 和 q 分别指向 list1 和 list2 的头节点
ListNode p = list1;
ListNode q = list2;
// 指针 t 指向当前合并后的链表的尾部
ListNode t = dummy;

// 遍历 list1 和 list2,直到其中一个链表为空
while (p != null && q != null) {
// 比较当前节点的值,将较小的节点连接到合并后的链表
if (p.val <= q.val) {
t.next = p; // 将 p 连接到 t 的后面
t = t.next; // t 前移到下一个位置
p = p.next; // p 前移到下一个位置
} else {
t.next = q; // 将 q 连接到 t 的后面
t = t.next; // t 前移到下一个位置
q = q.next; // q 前移到下一个位置
}
}

// 如果 list1 或 list2 还有剩余节点,将剩余部分连接到合并后的链表
t.next = (p == null) ? q : p;

// 返回合并后的链表头节点(跳过虚拟头节点 dummy)
return dummy.next;
}
}

23. 合并 K 个升序链表 - 力扣(LeetCode)

难度:困难

可以复用上个题的代码,K个升序链表,两两进行合并,合并到最后就是最终答案。

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
/**
* 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 mergeKLists(ListNode[] lists) {
// 如果链表数组为空,直接返回 null
if (lists.length == 0) return null;
// 遍历链表数组,将前两个链表合并,然后将结果存储在第二个位置
for (int i = 0; i < lists.length - 1; i++) {
// 将第 i 个链表和第 i+1 个链表合并
ListNode tmp = mergeList(lists[i], lists[i + 1]);
// 将合并后的链表存储在第 i+1 个位置
lists[i + 1] = tmp;
}
// 返回最终合并后的链表(存储在数组的最后一个位置)
return lists[lists.length - 1];
}

// 辅助函数
private ListNode mergeList(ListNode node1,ListNode node2){
ListNode dummy = new ListNode(-1);
ListNode p = node1,q = node2,t = dummy;
while(p!=null&&q!=null){
if(p.val<q.val){
t.next = p;
t = t.next;
p = p.next;
}else {
t.next = q;
t = t.next;
q = q.next;
}
}
t.next = (q==null) ? p : q;
return dummy.next;
}
}

两两合并的时间复杂度为O(n),对于K个链表要合并K次,时间复杂度为O(kn)。

但是当K值太大了,时间复杂度比较高,可以利用优先队列的方式进行优化。


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
/**
* 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 mergeKLists(ListNode[] lists) {
// 使用优先队列(最小堆)来存储节点,以便始终可以快速获取最小节点
Queue<ListNode> pq = new PriorityQueue<>((v1, v2) -> v1.val - v2.val);

// 将所有链表的头节点加入优先队列
for (ListNode p : lists) {
if (p != null)
pq.offer(p);
}

// 创建一个虚拟头节点,用于简化链表操作
ListNode dummy = new ListNode(-1);
ListNode t = dummy;

// 当优先队列不为空时,重复以下操作
while (!pq.isEmpty()) {
// 取出队列中最小的节点
ListNode p = pq.poll();
// 将该节点添加到结果链表中
t.next = p;
t = t.next;
// 如果该节点有下一个节点,则将下一个节点加入优先队列
if (p.next != null)
pq.offer(p.next);
}

// 返回合并后的链表
return dummy.next;
}
}

为什么要用优先队列?

优先队列(最小堆)能够快速获取和移除当前最小值,这是因为我们希望每次从多个链表中获取最小节点并将其添加到结果链表中。使用优先队列可以确保这个过程高效进行。

时间复杂度

使用优先队列的时间复杂度分析:

  • 将所有链表头节点加入优先队列的时间复杂度是 O(K log K),其中 K 是链表的数量。
  • 合并链表过程中,每次操作都是在优先队列中插入和删除节点。总共有 N 个节点需要操作,每次插入或删除的操作时间复杂度是 O(log K),因此总时间复杂度是 O(N log K)。

27. 移除元素 - 力扣(LeetCode)

难度:简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length;
int head = 0;
int tail = n - 1;

while (head <= tail) {
if (nums[head] == val) {
// 找到从后向前第一个不等于 val 的元素
while (head < tail && nums[tail] == val) {
tail--;
}
// 将其与当前 head 位置的元素交换
nums[head] = nums[tail];
tail--;
}
if (nums[head] != val) {
head++;
}
}
// 返回新数组的长度
return head;
}
}

注意边界情况

1
nums = [1] val = 1

26. 删除有序数组中的重复项 - 力扣(LeetCode)

难度:简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int removeDuplicates(int[] nums) {
int len = nums.length; // 获取数组长度
int idx = 0; // idx 指针用于记录数组中非重复元素的最后一个位置
int p = 0; // p 指针用于遍历数组

// 遍历数组
while(p < len) {
// 如果当前元素和 idx 位置的元素不同
if(nums[p] != nums[idx]) {
nums[++idx] = nums[p]; // 将当前元素放到 idx 的下一个位置
}
p++; // 移动 p 指针
}

// 返回数组中非重复元素的个数
return idx + 1;
}
}

80. 删除有序数组中的重复项 II - 力扣(LeetCode)

难度:中等

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
class Solution {
public int removeDuplicates(int[] nums) {
int len = nums.length; // 获取数组长度
int idx = 0; // idx 指针用于记录数组中元素的位置
int p = 1; // p 指针用于遍历数组,初始值为1,因为第一个元素总是保留的
int count = 1; // 计数器,用于记录当前数字出现的次数

// 遍历数组
while(p < len) {
if(nums[p] == nums[idx]) { // 如果当前元素与 nums[idx] 相同
count++; // 计数器增加
if(count <= 2) { // 只允许最多两个相同的元素
nums[++idx] = nums[p]; // 将当前元素放到 idx 的下一个位置
}
} else { // 如果当前元素与 nums[idx] 不同
nums[++idx] = nums[p]; // 将当前元素放到 idx 的下一个位置
count = 1; // 重置计数器
}
p++; // 移动 p 指针
}

// 返回数组中保留的元素个数
return idx + 1;
}
}

这两个问题虽然都涉及从排序数组中删除重复项,但它们的具体要求不同:

题目要求的不同点

  1. 上一个题目(保留唯一元素)
    • 题目要求从排序数组中删除重复项,使得每个元素只出现一次,并返回新的数组长度。
  2. 当前题目(最多保留两个重复元素)
    • 题目要求从排序数组中删除重复项,使得每个元素最多出现两次,并返回新的数组长度。

解决方法的不同点

  1. 上一个题目(保留唯一元素)

    • 使用一个指针 idx 来跟踪已处理的数组位置。
    • 使用另一个指针 p 来遍历整个数组。
    • 如果 nums[p] 不等于 nums[idx],则将 nums[p] 放到 idx 的下一个位置,并更新 idx
    • 最终返回 idx + 1 作为新的数组长度。
  2. 当前题目(最多保留两个重复元素)

    • 同样使用指针 idxp

    • 使用一个计数器 count 来记录当前元素出现的次数。

    • 如果 nums[p] 等于 nums[idx],则增加 count

      • 如果 count 小于或等于2,则将 nums[p] 放到 idx 的下一个位置,并更新 idx。(count意味idx所指的数在新数组中已经出现的次数)
    • 如果 nums[p] 不等于 nums[idx],则将 nums[p] 放到 idx 的下一个位置,重置 count 为1,并更新 idx

    • 最终返回 idx + 1 作为新的数组长度。


有更简单的写法,不需要用count变量记录出现次数:

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