归并排序
21.合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
JAVA代码:
//类似于归并排序的归并部分
//迭代法
//没有头指针
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//类似于归并排序的归并部分
//迭代法
//没有头指针
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1);
ListNode p = head;
while (l1!=null && l2!=null){
if (l1.val<=l2.val){
p.next = l1;
p = p.next;
l1 = l1.next;
}
else{
p.next = l2;
p = p.next;
l2 = l2.next;
}
}
//有且仅有一个列表没有遍历完
p.next = l1 == null ? l2 : l1;
return head.next;
}
}
148.排序链表
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
解答:
有时间复杂度O(nlogn), 想到归并排序
- 对链表进行切分并归并排序。
将链表切分成大小为1的多个子链表,两两排序合并,共2个元素合并。
将链表切分成大小为2的多个子链表,两两排序合并,共4个元素合并。
将链表切分成大小为4的多个子链表,两两排序合并,共8个元素合并。
一共切分 logn 次。 .
JAVA代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
//dummyhead:一次排序后链表
//curr:无序链表。
//prev: 当前排好的有序链表的尾结点。
ListNode dummyhead = new ListNode(-1);
dummyhead.next = head; //dummyhead的后面一个结点指向head
//链表长度
int len = listLength(head);
for (int step=1; step<=len; step<<=1){
ListNode curr = dummyhead.next;
ListNode prev = dummyhead;
while (curr!=null){
ListNode left = curr;
ListNode right = split(left, step); //从left切掉step个结点得到left.并返回切点,即right链表头结点
curr = split(right, step); //从right切掉step个结点得到right,并返回切点。
prev.next = merge(left, right);
while(prev.next!=null) prev = prev.next;
}
}
return dummyhead.next;
}
//切分链表: head前step个结点切掉作为left部分,返回后一个切点,即right部分的头节点
public ListNode split(ListNode head, int step){
ListNode curr = head;
for (int i=1; i<step && curr!=null; i++){
curr = curr.next;
}
if (curr==null) return null;
ListNode right = curr.next;
curr.next = null;
return right;
}
//二路归并
public ListNode merge(ListNode left, ListNode right){
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
while(left!=null && right!=null){
if (left.val<= right.val){
curr.next = left;
left = left.next;
curr = curr.next;
}
else{
curr.next = right;
right = right.next;
curr = curr.next;
}
}
curr.next = left==null? right: left;
return dummy.next;
}
//求链表长度
public int listLength(ListNode head){
int length = 0;
ListNode curr = head;
while (curr!=null){
curr = curr.next;
length++;
}
return length;
}
}
快速排序
75. 颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
JAVA代码:
class Solution {
public void sortColors(int[] nums) {
int red = -1;
int blue = nums.length;
int i = 0;
while (i<blue){
if (nums[i]==0){
swap(nums, i++, ++red);
}
else if(nums[i]==2){
swap(nums, i, --blue);
}
else i++;
}
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
堆排序
347. 前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题解:
1. 哈希表 + 小根堆
- 使用哈希表,统计每个元素出现的次数,元素作为键,出现的次数作为值。
- 将元素放入小根堆中,根据元素出现的次数进行排序,维持小根堆大小为k,最后得到的小根堆中的元素即为前k高频的元素。
- 遍历k个元素的小根堆,放入结果数组中。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
Map<Integer, Integer> map = new HashMap<>();
for (int num: nums){
map.put(num, map.getOrDefault(num, 0)+1);
}
// 堆排序:将元素放入小根堆中,按照元素的频率排序,并维持堆大小为k
// 重写compare方法
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer a, Integer b){
return map.get(a)-map.get(b);
}
});
// 小根堆,维持大小为k
for (int key: map.keySet()){
pq.offer(key);
if (pq.size() > k){
pq.poll();
}
}
// 访问小根堆中的元素
int[] res = new int[k];
int i = 0;
while(!pq.isEmpty()){
res[i++] = pq.poll();
}
return res;
}
}
时间复杂度O(n*logk)
空间复杂度O(n)
2. 哈希表 + 桶
- 哈希表存放各数字出现的频率。
- 准备若干个桶,每个桶存放出现次数相同的数字,桶的下标表示出现的次数。
- 只要倒序遍历
k
个非空桶即可得到前K
个高频元素。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int num: nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}
// 频率作为下标,将数放在桶中,频率越大的数越在后面
List<Integer>[] buckets = new ArrayList[nums.length + 1];
for (int key : map.keySet()) { // 返回map中所有key值的列表
int frequency = map.get(key);
if (buckets[frequency] == null){
buckets[frequency] = new ArrayList<>();
}
buckets[frequency].add(key);
}
// 获取频率前k高的元素
int[] res = new int[k];
int index = 0;
for (int i = buckets.length-1; index < k; i--){
if (buckets[i] == null)
continue;
for(int j = 0; j<buckets[i].size(); j++){
res[index++] = buckets[i].get(j);
}
}
return res;
}
}
时间复杂度O(n)
空间复杂度O(n)
215. 数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
题解:
1. 直接排序
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length-k];
}
}
时间复杂度O(nlogn)
空间复杂度O(1)
2. 堆排序:优先队列
维护小根堆的大小为k,最后队列中第一个元素即为第k大的元素。
class Solution{
public int findKthLargest(int[] nums, int k){
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int num: nums){
pq.add(num);
// 维护堆的大小
if (pq.size()>k){
pq.poll();
}
}
return pq.peek();
}
}
时间复杂度O(nlogk)
空间复杂度O(1)
3. 堆排序:大根堆
先对数组所有元素形成大根堆,然后再根下沉直到排好最大的k的元素。
class Solution{
public int findKthLargest(int[] nums, int k){
// 形成大根堆
for (int i = 0; i<nums.length; i++){
heapInsert(nums, i);
}
int heapSize = nums.length;
swap(nums, 0, --heapSize);
while(heapSize > nums.length-k){
heapify(nums, heapSize);
swap(nums, 0, --heapSize);
}
return nums[nums.length-k];
}
public void heapInsert(int[] nums, int index){
while(nums[index] > nums[(index-1)/2]){
swap(nums, index, (index-1)/2);
index = (index-1)/2;
}
}
public void heapify(int[] nums, int heapSize){
int index = 0;
int left = 1;
while(left < heapSize){
int right = left + 1;
int largest = right < heapSize && nums[left]<nums[right]
? right
: left;
largest = nums[largest] < nums[index]? index:largest;
if (largest == index)
break;
swap(nums, largest, index);
index = largest;
left = index*2+1;
}
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
时间复杂度O(nlogk)
空间复杂度O(1)
4. 快速选择
将无序部分nums[L: R]
的最后一个元素作为基准,小于等于基准的放在左边,大于基准的放在右边,如此就能知道该基准是第几小的数。设基准下标为posi
- 若
posi<k
,那么第k
大的数在nums[L+1, R]
区间 - 若
posi>k
, 那么第k
大的数在nums[L, R-1]
区间 - 若
posi == k
, 那么nums[posi]
正好为第k
大的数。
思路过程类似与快速排序,不同于快排的是,快速选择只要排左右其中一个区间,而快速排序左右两个区间都要排序。
class Solution{
public int findKthLargest(int[] nums, int k){
k = nums.length - k;
int L = 0;
int R = nums.length-1;
int posi = 0;
while(L<=R){
posi = position(nums, L, R);
if (posi == k){
break;
}
else if (posi < k){
L = posi + 1;
}
else
R = posi - 1;
}
return nums[posi];
}
public int position(int[] nums, int L, int R){
int less = L-1;
int more = R;
int i = L;
while(i < more){
if (nums[i] <= nums[R]){
i++;
less++;
}
else {
swap(nums, i, --more);
}
}
swap(nums, more, R);
int posi = more;
return posi;
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
时间复杂度平均情况O(n), 最坏情况O(n^2)
空间复杂度O(1)
桶排序
451. 根据字符出现频率排序
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
示例 1:
输入:
"tree"
输出:
"eert"
解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入:
"cccaaa"
输出:
"cccaaa"
解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入:
"Aabb"
输出:
"bbAa"
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
题解:哈希表 + 桶
- 将字符和对应出现的频率存放在哈希表中:{字符:频率}
- 将不同频率的字符装进相应的桶中,桶的下标表示不同的频率
- 倒序遍历桶中的字符,即按照频率降序的方式遍历,根据字符及对应的频率拼接成结果字符串。
class Solution {
public String frequencySort(String s) {
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i<s.length(); i++){
char ch = s.charAt(i);
map.put(ch, map.getOrDefault(ch, 0)+1);
}
List<Character>[] buckets = new ArrayList[s.length()+1];
// 将不同频率的字符装进相应的桶中
for (Character key: map.keySet()){
int fq = map.get(key);
if (buckets[fq] == null){
buckets[fq] = new ArrayList<>();
}
buckets[fq].add(key);
}
// 倒序遍历桶
StringBuffer sb = new StringBuffer();
for(int i = buckets.length-1; i>0; i--){
if (buckets[i] == null)
continue;
for (int j = 0; j<buckets[i].size(); j++){
for (int k = 0; k<i; k++){
sb.append(buckets[i].get(j));
}
}
}
return sb.toString();
}
}
时间复杂度O(n)
空间复杂度O(n)