第三部分:排序
21.合并两个有序链表(简单)
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
第一种思路:
之前上数据结构了解过一些算法,但是都不是很熟悉,不过这道题用递归还是知道的(链表应该蛮常使用递归吧),思路很易懂:
首先检查两个链表的头节点
list1
和list2
:
如果
list1
为空,则返回list2
。因为如果一个链表为空,合并结果就是另一个非空链表。如果
list2
为空,则返回list1
,逻辑与前面相同。如果两个链表都不为空,比较
list1
和list2
的头节点值:
如果
list1.val
小于list2.val
,则将list1
作为合并后的链表的头节点,接着递归合并list1.next
和list2
,将结果连接到list1
的next
。否则,将
list2
作为合并后的链表的头节点,递归合并list1
和list2.next
,将结果连接到list2
的next
。每次返回的都是较小的节点,并且通过递归调用连接了剩下的部分,这样最终合并后的链表会保持升序。
/**
* 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; } // 带值和下一个节点的构造函数
* }
*/
// ListNode 是链表节点的定义,每个节点包含一个整数值 val 和一个指向下一个节点的引用 next。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 如果 list1 为空,直接返回 list2
if (list1 == null) {
return list2;
}
// 如果 list2 为空,直接返回 list1
else if (list2 == null) {
return list1;
}
// 如果 list1 的节点值小于 list2 的节点值
else if (list1.val < list2.val) {
// 递归地合并 list1 的下一个节点和 list2,并将结果链接到 list1
list1.next = mergeTwoLists(list1.next, list2);
// 返回 list1,因为它的节点值小于 list2
return list1;
} else {
// 如果 list2 的节点值小于或等于 list1 的节点值
// 递归地合并 list1 和 list2 的下一个节点,并将结果链接到 list2
list2.next = mergeTwoLists(list1, list2.next);
// 返回 list2,因为它的节点值小于或等于 list1
return list2;
}
}
}
229.多数元素 II(中等)
题目:给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋
次的元素。
示例 1:
输入:nums = [3,2,3] 输出:[3]
示例 2:
输入:nums = [1] 输出:[1]
示例 3:
输入:nums = [1,2] 输出:[1,2]
第一种思路:
首先想到的其实是哈希Set,但是觉得可能会占较大空间开销,就先放下了这种思路。
然后想到了在循环中比较这个数出现次数是否大于 n / 3 ,然后自然地想到了先将数组进行排序,
这样由于相同元素都在一起了,这样就方便比较了:
设定一个变量
times
为nums.length / 3
, 这是我们需要的阈值(即元素出现次数必须超过这个值)。
temp_times
初始化为 1,用于记录当前元素的出现次数,从索引 1 开始遍历数组,比较当前元素与前一个元素:
如果相等,则
temp_times++
增加计数。如果不相等,将
temp_times
重置为 1,并继续。每当
temp_times
超过times
,并且在mylist
中不存在当前元素的前一个值(nums[i-1]
),则将该元素添加到结果列表mylist
中。后来发现案例有数组长度小于3的,然后想到可以直接先判断:
长度小于3: 首先检查输入数组
nums
的长度。如果长度小于 3,所有元素都可能是多数素,因此直接遍历数组,将不重复的元素添加到mylist
中。
数组长度大于或等于 3:接下来使用
Arrays.sort(nums)
将数组排序。这一步是关键,因为排序后的数组使我们可以顺序检查元素的出现次数。
class Solution {
public List<Integer> majorityElement(int[] nums) {
// 创建一个列表用于存储结果
List<Integer> mylist = new ArrayList<>();
// 如果数组长度小于 3,所有元素都有可能是多数元素
if (nums.length < 3) {
for (int num : nums) {
// 只将不重复的元素添加到结果列表中
if (!mylist.contains(num))
mylist.add(num);
}
return mylist; // 返回结果列表
}
// 对数组进行排序
Arrays.sort(nums);
// 计算出现次数的阈值,即 n / 3
int times = nums.length / 3;
int temp_times = 1; // 当前元素的出现次数
// 从索引 1 开始遍历数组
for (int i = 1; i < nums.length; i++) {
// 如果当前元素与前一个元素相等,增加计数
if (nums[i] == nums[i - 1]) {
temp_times++;
} else {
// 否则,重置计数为 1,继续遍历
temp_times = 1;
continue;
}
// 如果当前元素的出现次数超过阈值
// 且结果列表中不包含当前元素的前一个值,添加到结果列表
if (temp_times > times && !mylist.contains(nums[i - 1])) {
mylist.add(nums[i - 1]);
}
}
// 返回包含所有出现次数超过 n / 3 的元素的结果列表
return mylist;
}
}
后来经过代码分析后:
代码逻辑中的潜在问题与改进:
重复元素检测:当前方法可能会导致遗漏一些应被视为多数元素的值,尤其是在最后一个元素的出现次数只有在数组结尾时检查。可以考虑在遍历结束后再检查最后一个值。
效率:虽然排序的时间复杂度为
O(n log n)
,整体空间复杂度为O(n)
,你可以考虑使用哈希表或摩尔投票算法来提高效率,使时间复杂度降低到O(n)
。最坏情况下的性能:如果数组中所有元素都相同,且更高的元素数量传递了少量相同的元素,可能会导致错误的判断。可以通过使用 HashMap 来精确地记录每个元素的计数。
原来用哈希表时间复杂度更低啊!!!
class Solution {
public List<Integer> majorityElement(int[] nums) {
// 创建一个列表用于存储结果
List<Integer> mylist = new ArrayList<>();
// 创建一个哈希表用于存储每个元素的出现次数
Map<Integer, Integer> countMap = new HashMap<>();
// 计算阈值,表示出现次数超过 n / 3 的元素
int threshold = nums.length / 3;
// 遍历输入数组,计数每个元素的出现次数
for (int num : nums) {
// 将元素 num 的出现次数更新到 countMap 中
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
// 遍历 countMap,检查每个元素的出现次数是否超过阈值
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
// 如果当前元素的出现次数大于阈值,则将其添加到结果列表中
if (entry.getValue() > threshold) {
mylist.add(entry.getKey());
}
}
// 返回包含所有出现次数超过 n / 3 的元素的结果列表
return mylist;
/*
上面这一段添加到结果集中的代码也可以用下面的代码替换
List<Integer> ans = new ArrayList<>();
for (int x : cnt.keySet()) {
if (cnt.get(x) > nums.length / 3) {
ans.add(x);
}
}
return ans;
*/
}
}
虽然时间复杂度确实降低为O(n)了,但是执行用时没我的方法少好吧 ~╮(╰ - ╯)╭~