1 2 3 4

牛客TOP101刷题记录

 一、排序总结

排序算法大致可以分为两类:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序;如果需要使用外存,则称为外排序。

内排序有以下几类:

  1. 插入排序:直接插入、二分法插入、希尔
  2. 选择排序:直接选择、堆排序
  3. 交换排序:冒泡、快速
  4. 归并排序
  5. 基数排序

排序方法

时间复杂度(平均)

时间复杂度(最坏)

时间复杂度(最好)

空间复杂度

稳定性

复杂性

直接插入排序

O(n2)

O(n2)

O(n)

O(1)

稳定

简单

希尔排序

O(nlog2n)

O(n2)

O(n1.3)

O(1)

不稳定

较复杂

直接选择排序

O(n2)

O(n2)

O(n2)

O(1)

不稳定

简单

堆排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(1)

不稳定

较复杂

冒泡排序

O(n2)

O(n2)

O(n)

O(1)

稳定

简单

快速排序

O(nlog2n)

O(n2)

O(nlog2n)

O(nlog2n)

不稳定

较复杂

归并排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(n)

稳定

较复杂

基数排序

O(d(n+r))

O(d(n+r))

O(d(n+r))

O(n+r)

稳定

较复杂

1.插入排序

思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的合适位置,直到全部插入排序完为止。

关键点:在前面已经排好序的序列中找到合适的位置插入。

 

二分查找排序,是在普通插入排序的基础上,引入了二分查找的思想。

希尔排序是对直接插入排序的改良,关键点在于,让较大或者较小的数,快速靠近自身所属的位置。

 

2.选择排序

思想:每趟从待排序的记录序列中选择关键字最小的记录放置到已排序序列的最后位置,直到全部排完

关键点:找最小的记录

 

堆排序:通过堆,这种可以快速找到最小的记录的方式。

 

3.交换(冒泡)排序

思想:利用交换元素的位置进行排序,每次两两比较待排序的元素,直到全部排完

关键问题:理清楚需要进行几轮排序

 

快速排序:通过找分界点,将较大的元素和较小的元素快速区分开,直至各区间只有一个数

 

4.归并排序

思想:使用了分治法。分:将待排序的序列进行“对半”分,直至各区间只有一个数;治:将有序序列进行合并。

关键问题:分与治

 

5.基数排序

思想:将待比较的正整数统一为同样的数位长度,短的补零。然后从最低位开始,依次进行一次排序;从最低位到最高位。

关键问题:进制和数位

 

计数排序:适用于范围比较小的整数序列。找到最小值和最大值,然后以此建立数组,统计出现个数,然后排序。

桶排序:遍历数组,找到最小值和最大值,然后依次划分为多个区间(桶),在桶里单独排序。

二、TOP101

题目来源:热门100题

 

1.链表

1.反转链表

两种反转方法:

关键点:三个指针,pre, cur,next

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
//关键点:三个指针,pre, cur,next ListNode
*pre = nullptr; ListNode *cur = pHead; ListNode *nex = nullptr; // 这里可以指向nullptr,循环里面要重新指向 while (cur) { nex = cur->next; cur->next = pre; pre = cur; cur = nex; } return pre; } };
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* dummy = new ListNode(0);
        dummy->next = pHead;
        
        ListNode* pre = dummy, *cur = pHead;
        while(cur->next){
            ListNode* temp = cur->next;
            cur->next = temp->next;
            temp->next = pre->next;
            pre->next = temp;
        }
        return dummy->next;
    }
};

关键点:添加虚拟头结点,在头上反转

2.指定区间反转

关键点:虚拟头节点简化操作,使用第二种反转方法

ListNode* dummy = new ListNode(0)

3.每k个一组反转

关键点:特殊情况处理(k == 1, head == nullptr, cur == nullptr)

 1 /**
 2  * struct ListNode {
 3  *    int val;
 4  *    struct ListNode *next;
 5  * };
 6  */
 7 
 8 class Solution {
 9 public:
10     /**
11      * 
12      * @param head ListNode类 
13      * @param k int整型 
14      * @return ListNode类
15      */
16     void reverse(ListNode* list){
17         ListNode* pre = nullptr, *cur = list, *next;
18         while(cur){
19             next = cur->next;
20             cur->next = pre;
21             pre = cur;
22             cur = next;
23         }
24     }
25     
26     ListNode* reverseKGroup(ListNode* head, int k) {
27         if(!head || k == 1) return head;
28         // write code here
29         ListNode* dummy = new ListNode(0);
30         dummy->next = head;
31         
32         ListNode* pre = dummy, *cur = head, *next = cur, *next_head;
33         while(next->next){
34             for(int i = 1; i < k; i++){
35                 if(next->next){
36                     next = next->next;
37                 }else{
38                     return dummy->next;
39                 }
40             }
41             next_head = next->next;
42             pre->next = nullptr;
43             next->next = nullptr;
44             reverse(cur);
45             //connect
46             pre->next = next;
47             cur->next = next_head;
48             //change
49             pre = cur;
50             cur = next_head;
51             next = cur;
52             if(cur == nullptr) break;
53         }
54         return dummy->next;
55     }
56 };
View Code

 4.合并两个链表

关键点:两个指针指向两个链表的未排序节点

 

5.合并k个已排序的链表

三种解法:分治;顺序合并;优先队列

这道题目可以作为分治法的练习题。

关键点:分成两个功能函数,合并函数以及划分函数

 

6.判断链表中是否有环

关键点:快慢指针,两指针相遇则说明有环。

 

7.链表中环结点的入口结点

关键点:第一次相遇说明有环,快指针返回,再次相遇则是环入口

 

 8.链表中倒数最后k个结点

关键点:双指针

 

9.删除链表的倒数第n个结点

关键点:创建虚拟头结点来避免分类讨论,找到需要删除的结点的前一个结点

 

10.两个链表的第一个公共结点

关键点:确认两个链表的长度,然后将链表对齐

 

11.链表相加

关键点:先反转链表,再相加,直至两链表均为空,且无进位。

 

12.单链表的排序

关键点:使用分治法;用快慢指针将链表进行 “ 分 ”,合并有序链表为 “ 治 ”

出错点:head 和 !head的具体含义

 

13.判断一个链表是否为回文结构

关键点:反转(折叠),然后对比

 

14.链表的奇偶重排

关键点:终止条件为指向偶数项及其后一项的指针不为空

 

15.删除有序链表中重复的元素1

关键点:需要熟练,

16.删除有序链表中重复的元素2

关键点:找到要删除的元素的前一个元素。

 

2.二分查找/排序

17.二分查找

关键点:记录左右边界,查找中点,然后压缩范围。l <= r, 注意等于情况

18.二维数组中的查找

关键点:利用性质,从左下角或者右上角出发寻找

错误点:for循环的判断条件要用逻辑运算符链接,不能用逗号

19.寻找峰值

关键点:二分查找,压缩范围。

 

20.数组中的逆序对

关键点:归并排序;在最外层开辟一个足够大的数组,然后传引到函数

 

21.旋转数组的最小数字

二分查找

关键点:注意判断等于的情况

 

22.比较版本号

关键点:注意循环终止条件应该为:或逻辑

 

3.二叉树

23.前序遍历

 

/**
 * struct TreeNode {
 *    int val;
 *    struct TreeNode *left;
 *    struct TreeNode *right;
 *    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:

    void preorder(vector<int> &vec, TreeNode* root){
        if(root == NULL) return;
        
        vec.push_back(root->val);
        preorder(vec, root->left);
        preorder(vec, root->right);
        return;
    }
    
    vector<int> preorderTraversal(TreeNode* root) {
        // write code here
        vector<int> res;
        preorder(res, root);
        return res;
    }
};
View Code

 

24.中序遍历(同上)

25.后序遍历(同上)

26.层序遍历

关键点:用队列记录每一层的结点

27.按之字形顺序打印二叉树

关键点:层序遍历,偶数行翻转

 

28.二叉树的最大深度

解法:递归,层序遍历

 

29.二叉树中和为某一值的路径1

关键点:递归

 

30.二叉搜索树与双向链表

关键点:找到最小值,初始化head与pre;双指针,指向当前结点和前置结点,互相连接,更新前置结点为当前结点

 

31.对称的二叉树

关键点:递归,两个指针分别遍历两子树

 

32.合并二叉树

关键点:重新弄构建整个二叉树

 

33.二叉树的镜像

 

34.判断是不是搜索二叉树

 

35.判断是不是完全二叉树

 

36.判断是不是平衡二叉树

 

37.二叉搜索树的最近公共祖先

 

38.在二叉树中找到两个结点的最近公共祖先

 

39.序列化二叉树

 

40.重建二叉树

 

41.输出二叉树的右视图

 

 

4.堆/栈/队列

42.用两个栈实现队列

 

43.包含min函数的栈

用一个额外的栈保存最小元素

 

44.有效括号序列

思路:设置一个栈,遇到左括号,则在栈中加入右括号;遇到右括号则判断栈中是否有元素且栈顶元素与右括号一致。

 

45.滑动窗口的最大值

思路:用一个双向队列,每个元素入队前,去除前面小于它的元素的下标,再将自己的下标入队。

出错点:注意下标和值不要混淆

 

46.最小的k个数

思路:砸死优先级队列中放入k个数,遍历数组,若遇到小于队列中最大的数,则置换;

 

47.寻找第k大的数

思路:使用快排,当间隔元素为第k个元素时,则返回

 

48.数据流的中位数

思路:插入时排序,取中位数判断奇偶

错误点:偶数时中位数是 n/2 与 n/2-1

 

49.表达式求值

思路:栈,只要求三种运算的情况下,可以只用一个栈

两个栈:一个存放数字,另一个存放运算符;遍历字符,遇到空格就跳过,(则加入栈,)则计算栈内运算,直至遇到(,数字则将整个数字取出入栈,运算符则判断优先级,进行入栈或运算;栈内运算符优先级高时,才运算;小技巧:为了避免讨论负数,可以用(0 -  替代( -

 

5.哈希

50.两数之和

思路:哈希表的查找

 

51.数组中出现次数超过一半的数

思路:num记录数字,count计数,遇到相同数字count++,否则--,当count为0时更新num,count置为1。

 

52.数组中只出现一次的两个数字

思路:异或运算,

关键点:寻找最右边的比特为1的位   rightone = eor & ( ~eor + 1)

 

53.缺失的第一个正整数

思路:排序,对比

 

54.三数之和

思路:先排序,然后双指针压缩空间

注意点:去重,left 指针不能超过right指针

 

6.递归/回溯

55.没有重复项数字的全排列

思路:选择与撤销

class Solution {
public:
    void recursion(vector<vector<int>> &res, vector<int> &num, int index){
        if(index == num.size()-1) res.push_back(num);
        else {
            for(int i = index; i < num.size(); i++){
                swap(num[i], num[index]);
                recursion(res, num, index+1);
                swap(num[i], num[index]);
            }
        }     
    }
    
    vector<vector<int> > permute(vector<int> &num) {
        vector<vector<int>> res;
        sort(num.begin(), num.end());
        recursion(res, num, 0);
        return res;
    }
};
View Code

 

56.有重复项数字的全排列

思路:与上一题相同,需要额外一个数组保存元素是否访问过,用于去重

 

57.岛屿数量

思路:遇到1,count++,然后深度优先遍历,将所有相邻1置为0。

 

58.字符串的排列

思路:与56一致

 

59.N皇后问题

思路:一个子函数判断皇后是否满足条件;递归

 

60.括号生成

思路

 

61.矩阵最长递增路径

 

7.动态规划

62.斐波那契数列

63.跳台阶

 

64.最小花费爬楼梯

思路:动态规划,dp数组,状态转移方程为

 

65.最长公共子序列

算法导论

 

66.最长公共子串

 

67.不同路径的数目

递归

 

68.矩阵的最小路径和

dp[i][j]保存了以当前i, j 为终点的最短路径长度

 

69.把数字翻译成字符串

dp[i]表示前 i 个数的翻译结果数

 

70.兑换零钱

dp[i] 表示要凑齐 i 元最少需要多少货币数

 

69.把数字翻译成字符串

 

posted @ 2022-04-14 13:15  木木木999  阅读(126)  评论(0)    收藏  举报