牛客网模板速刷TOP101——个人笔记

  1. 反转链接
    创建表头,逐个反转

  2. 链表内指定区间反转
    先移动到至于区域开头,逐个反转到区域结尾

  3. 链表中的节点每k个一组翻转

    1. 模拟法
      构建了reverse函数对于[begin,end]内的节点进行翻转,由于翻转过程后,前一个节点和后一个节点会对该段链表的链接丢失,所以要先记录前后节点,翻转后重新链接上。

    2. 每k装入栈取出重新链接
  4. 合并两个排序的链表

    1. 迭代法
      一般创建单链表,都会设一个虚拟头结点,也叫哨兵,因为这样每一个结点都有一个前驱结点。两个链表头中小的插入哨兵后面,直到有一方全部插入结束,另一方的头节点插在最后。
  5. 合并k个已排序的链表

    1. 顺序排序

      核心语句:return mergeTwoLists(mergeKLists(lists, begin, mid), mergeKLists(lists, mid + 1, end));
      其他与第四题差不多

  6. 判断链表中是否有环

    1. 快慢指针
      两个指针都指向链表头,一个走一步,另一个走两步,如果相遇则代表有环
    2. 哈希表
      构建哈希表,即可查阅是否经历过该节点
  7. 链表中环的入口节点

    1. 快慢指针
      与第六题不同的是,两个指针相遇后,将慢指针重新移到链表头,之后两个指针都走一步,相遇点就是环的入口。
    2. 哈希表
      构建哈希表,第一次重复的节点就是起点
  8. 链表中倒数最后k个节点

    1. 双指针
      先让快指针走k - 1步,注意原长度可能没有k
  9. 删除链表的倒数第n个节点

    1. 双指针
      和上一题唯一的差别是,删除第n个节点,就需要多一个前置节点用于链表的连接。
  10. 两个链表的第一个公共结点

    1. 路线交换
      A走完走B,B走完走A,相遇的时候就是第一个公共结点
    2. 哈希表
      记录完A的路线,B中第一个重复的结点就是第一个公共结点
  11. 链表相加(二)


    1. 两个栈分别存放两个链表的各个结点,实现从右往左相加,需要注意的地方是进位问题。同时多两个循环处理其中一个链表多余的位数。其中为了节省空间,可以在第一链表上进行数值操作。
    2. 链表反转
      两个链表翻转后依次相加,道理上和用栈一样
  12. 单链表的排序

    1. 归并排序
      快慢指针到链表中点后进行切割,然后分别对链表进行递归,然后两两进行合并。
    2. 利用辅助数组
      遍历存储各个结点的值,排序后新建一个链表
  13. 判断一个链表是否为回文结构

    1. 链表反转
      将链表后半部分进行反转,然后前后依次进行比较。期间同样是利用快慢指针找到后半截链表的开头。
    2. 利用辅助数组
      遍历存储各个结点的值,首尾向中间进行比对
  14. 链表的奇偶重排

    1. 双指针
      一个指向head,一个指向head→next,然后互相指向另一者的next
  15. 删除有序链表中重复的元素

    1. 双指针
      前后同时移动删除即可
    2. 延伸——如果是无序链表的话
      使用哈希表进行存储,出现过就跳过
  16. 删除链表中的所有重复出现的元素

    1. 双指针
      和上一题相差不大,多一个哨兵记录重复部分的前一个结点,并且过程中哨兵要不断更新
  17. 二分查找-I

    1. 最直接的二分查找
  18. 二维数组中的查找

    1. 从右上开始找,比目标值大向左,比目标值小向下
  19. 寻找峰值(单边峰值也算峰值)

    1. 二分查找
      首先查看首尾是否为峰值,不是的话开始二分查找,比右边小就往右找,否则往左找(往左找保留自身是峰值的可能)
    2. 找最大值
      直接比较找出最大值
  20. 数组中的逆序对

    1. 归并排序
      很经典的题,总体是归并排序,两两比较合并的期间,左边比右边大,那么左边的后面都会比右边的这个数大,增加的逆序对数量就是左边这个数到最后一共的数字个数
  21. 旋转数组的最小数字

    1. 二分查找
      中间比右边大朝右边走,中间比右边小往左边走(保留自身是最小的可能)
  22. 比较版本号

    1. 双指针
      先在两个版本号的末尾补上’.'保证格式一致,分别取出两个版本号的一部分进行比较
  23. 二叉树的前序遍历

      ans.push_back(root -> val);
      preorderTraversal(root -> left);
      preorderTraversal(root -> right);
    
    
  24. 二叉树的中序遍历

      preorderTraversal(root -> left);
      ans.push_back(root -> val);
      preorderTraversal(root -> right);
    
  25. 二叉树的后序遍历

      preorderTraversal(root -> left);
      preorderTraversal(root -> right);
      ans.push_back(root -> val);
    
  26. 求二叉树的层序遍历

    1. 队列
      队列存储每一层的结点,然后遍历结点存储下一层结点,知道遍历所有结点。
  27. 按之字形顺序打印二叉树

    1. 队列
      和上一题的方法一样,但是偶数层获取的值进行reverse
  28. 二叉树的最大深度

    1. 深度优先搜索
      DFS存储深度,记录最大深度
  29. 二叉树中和为某一值的路径

    1. 深度优先搜索
      DFS记录路径和,如果是叶子结点,进行比较
  30. 二叉搜索树与双向链表

    1. 中序遍历
      动态数组存储二叉搜索树中序遍历存储树的结点,然后更新每个结点的前后结点
  31. 对称的二叉树

    1. 遍历
    isPart(left -> left, right -> right) && isPart(left -> right, right -> left);
        }
    
  32. 合并二叉树

    1. 递归
      1. 递归合并t1、t2的左右节点。
      2. 两个节点的值求和并赋给t1
  33. 二叉树的镜像

    1. 递归
      从下往上逐个交换左右结点
  34. 判断是不是二叉搜索树

    1. 中序遍历
      如果是二叉搜索树那么中序遍历一定是升序的
  35. 判断是不是完全二叉树

    1. 广度优先搜索
      层次遍历,左边的节点(上一行最后一个节点)不存在,但下一个节点存在,则肯定不是完全二叉树。用pre存储上一个结点
  36. 判断是不是平衡二叉树

    1. DFS递归
      全局变量flag记录是否存在不平衡
      int leftLevel = DFS(root -> left) + 1;
      int rightLevel = DFS(root -> right) + 1;
      if(abs(leftLevel - rightLevel) > 1)
        flag = false;
    
  37. 二叉搜索树的最近公共祖先

    1. 二叉搜索树
      如果是公共祖先,那么肯定比其中一个大,比另一个小,或者等于其中一个
  38. 二叉树的最近公共祖先

    1. DFS
      l || r || (root->val == o1 || root->val == o2);
      其中l是左子树是否找得到结点,r是右子树能否找到结点
  39. 序列化二叉树

    1. 先序遍历
      假设序列化的结果为字符串 str, 初始str = “”.根据要求,遇到nullptr节点,str += “#” 遇到非空节点,str += “val” + “!”; 假设val为3, 就是 str += “3!”
      反序列化的结果,就是根据先序遍历,再重建二叉树即可。
  40. 重建二叉树

    1. 递归
            vector<int> vinLeft(vin.begin(), vin.begin() + rootPos);
            vector<int> vinRight(vin.begin() + rootPos + 1, vin.end());
            vector<int> preLeft(pre.begin() + 1, pre.begin() + 1 + vinLeft.size());
            vector<int> preRight(pre.begin() + 1 + vinLeft.size(), pre.end());
            root -> left = reConstructBinaryTree(preLeft, vinLeft);
            root -> right = reConstructBinaryTree(preRight, vinRight);
    
  41. 输出二叉树的右视图

    1. 层次遍历
      层次遍历输出每层最后一个结点,本题主要是还要重建二叉树
  42. 用两个栈实现队列

    1. 栈的基础知识

      push操作就直接往stack1中push, pop操作需要分类一下:如果stack2为空,那么需要将stack1中的数据转移到stack2中,然后在对stack2进行pop,如果stack2不为空,直接pop就ok。

  43. 包含min函数的栈

    1. vector数组
      利用vector数组存储每次更新后的最小值
  44. 有效括号序列

    1. 辅助栈

      1)引入辅助栈stk,遍历字符串,每次遇到’(’,’{’,’[‘字符的时候将字符入栈stk
      2)当遇到’)’,’}’,’]'字符的时候,则检查栈是否空,且顶元素是否为匹配元素(如{和}匹配等),如果栈空或者栈顶元素不为匹配元素则括号序列不合法
      3)当栈非空,且栈顶元素为匹配元素,则栈顶元素出栈。
      4)循环匹配字符串,直到每次字符处理完
      5)检查栈stk是否为空,栈为空则序列合法,否则不合法(当括号以正确顺序关闭时则最后的栈为空)

  45. 滑动窗口的最大值

    1. 双向队列
      用双向队列存储最大值的序号,而不是最大值,这样可以根据最大值的序号判断是否要删除该最大值,每次添加最大值时,删除末尾比当前添加的值要小的
  46. 最小的K个数

    1. 最大堆
      构建最大堆,先添加k个数进最大堆,之后如果有比堆顶值小的,那么删除堆顶,添加新值
  47. 寻找第K大

    1. 快速排序

      quick_sort(a, begin, right - 1);

      quick_sort(a, right + 1, end);

  48. 数据流中的中位数

    • 当我们已经添加的数据个数为奇数个时,小顶堆的数据个数比大顶堆多一个,此时若要再添加一个数据,必定添加在小顶堆Rmin上,**但是我们不知道当前添加的数是否大于中位数,故要先可插入Lmax然后将Lmax中的最大数放入Rmin中,同时删除Lmax中的最大数,即删除堆顶

    • 当我们已经添加的数据个数为偶数个时,小顶堆的数据个数等于大顶堆,此时若要再添加一个数据,必定添加在大顶堆Lmax上,添加的手段和前者相同。

      这样我们便将数据进行了分隔,然后通过O(1)的复杂度访问堆顶即可计算出中位数。

  49. 没写出来

  50. 两数之和

    1. 哈希表
      记得复习一下unordered_set和unordered_map
  51. 数组中出现次数超过一半的数字

    1. 经典摩尔计数法
      标记一个数字,记录它的出现次数,如果有不同的数字出现,那么次数减一,如果次数减为0,就标记下一个数字
  52. 数组中只出现一次的两个数字

    1. 位运算
      第一步、全部数字进行异或运算得到的结果相当于两个数字的异或结果
      第二步、找出第一步中异或结果中是1的最低位
      第三步、从全部数字中找出同一位上是1的数进行异或,就能得到其中一个数字
      第四步、将这个数字与第一步中的异或结果再次异或,剩下的就是另一个数字
  53. 缺失的第一个正整数

    1. 负数置为n + 1避免干扰,相当于目前数组内全为正数
      如果num出现过了,将对应序号内的值置为负数
      那么循环结束只剩下没有出现过的序号内会为正数
  54. 三数之和

    1. 双指针
      很正常的一道双指针,注意特殊情况
  55. 没有重复项数字的全排列

    1. 回溯+unordered_map
      算是毕竟经典的回溯,但是一般回溯只会更新数组中的值,这里还需要更新hash表里的对应数字。哈希表中标记该数字是否出现。用的时候置为true,用完置为false
  56. 有重复项数字的全排列

    1. 回溯+map
      上一题不会重复,所以标记数字是否出现就行,但这题会重复,所以是存储数字出现的个数。并且每次遍历的不是原数组了,而是map。
  57. 岛屿数量

    1. 递归
      遍历二维数组,找到’1’的时候开始递归并且岛屿数量加一,向四周寻找同样为’1’的字符,全部替换成0。
  58. 字符串的排列

    1. 回溯+map(如果字符串不需要按字典序排序也可以用unordered_map)
      首先遍历原字符串,map中存储每个字符出现的次数。然后遍历map,使用该字符则次数减一。
  59. N皇后问题

    1. 回溯+unordered_set
      特点是,需要3个set,分别存储列,正斜线,反斜线上的占用情况
  60. 括号生成

    1. 递归
      已经左括号数量一定大于等于右括号,所以当左括号数量小于总共允许的左括号数量是,加一个左括号递归,同时,右括号数量小于左括号时,加一个右括号递归
  61. 矩阵最长递增路径

    1. 递归
      除了边界判断情况外,设置了一个pre,存储上一个的数组,如果当前数字小于等于pre,则终止递归,小于是因为不满足递归条件,等于除不满足以外,还避免了往回走
  62. 斐波那契数列

    1. 动态规划
  63. 跳台阶

    1. 动态规划
      只不过第二个数是2
  64. 最小花费爬楼梯

    1. 动态规划
      跳台阶的姊妹版
      • 状态转移: 每次到一个台阶,只有两种情况,要么是它前一级台阶向上一步,要么是它前两级的台阶向上两步,因为在前面的台阶花费我们都得到了,因此每次更新最小值即可,转移方程为:dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])。
  65. 最长公共子序列

    1. 动态规划
      属于是不开题解做不来的题目,创建dp表,第一行第一列扩出来填入预制位0。剩下的对应行和列进行比较,dp[i][j]代表从这个往前最多有dp[i][j]个数相等,如果s1[i -1]和s2[j - 1]相等的话,那么取dp[i - 1][j - 1] + 1,不能取max(dp[i - 1][j], dp[i][j - 1]),因为不能同时取相同位置。
      接下来从表右下角开始,如果左和上都相等,移动到左上角,不相等,则往数值大的走。遇到s1[i -1]和s2[j - 1]的地方,将该字符加入字符串。
  66. 最长公共子串

    1. 动态规划
      其实就是暴力遍历的优化,i走字符串1,j走字符串2,找到相同的时候标记i和j,然后同时往下走,不相等的时候比较长度,如果大于最大长度就记录长度和字符串
  67. 不同路径的数目(一)

    1. 动态规划
      第一行和第一列置为1,dp[1][1]开始计算,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
  68. 矩阵的最小路径和

    1. 动态规划
      初始化第一行第一列,然后dp[1][1]开始计算,dp[i][j] = min(dp[i - 1][j] + dp[i][j - 1]) + matrix[i][j]
  69. 把数字翻译成字符串

    1. 动态规划
      只要注意边界情况已经几种含零的特殊情况就可以了
  70. 兑换零钱(一)

    1. 动态规划
      外层循环是硬币数值,内层循环从硬币数值到总金额dp[i] = min(dp[i], dp[i - num] + 1);
  71. 最长上升子序列(一)

    1. 动态规划
    for(int i = 0; i < size; ++i) {
                for(int j = 0; j <= i; ++j) {
                    if(arr[i] > arr[j]) {
                    只要前面某个数小于当前数,则要么长度在之前基础上加1,要么保持不变,取较大者
                        dp[i] = max(dp[i], dp[j] + 1);
                        if(dp[i] > maxLen)//比当前最大长度更长,则更新长度
                            maxLen = dp[i];
                    }
                }
            }
    
  72. 连续子数组的最大和

    1. 动态规划
      array[i] = max(array[i - 1] + array[i], array[i]);
  73. 最长回文子串

    1. 中心扩散
      分两种情况,奇数长度和偶数长度。奇数长度遍历数组,每个都慢慢往外扩一个长度,直到不相等。偶数长度采用i, i + 1,两者相同时,同理慢慢往外扩
  74. 数字字符串转化成IP地址

    1. 回溯
      isValid(int left, int right, string s)//检测[left, right]是否有效
      回溯有三种情况,长度为1,长度为2,长度为3
  75. 编辑距离(一)

    1. 动态规划
    if(str1[i - 1] == str2[j - 1])
      [i][j] = dp[i - 1][j - 1];
    else
      dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
    
     看了题解,相等的话,不需要进行操作,但如果不相等,肯定要插入、删除、修改选一个其中步骤最少的,然后加一
    
  76. 正则表达式匹配
    不会,考到就寄

  77. 最长的括号子串

    1. 动态规划
    for(int i = 1; i < len; ++i) {
        if(s[i] == ')') {    //左括号默认都为0
            if(s[i - 1] == '(') {
                dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
            }
            else if(i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '(') {
                dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
            }
            maxLen = max(maxLen, dp[i]);
        }
    }
    

    左括号都默认置为0,只有遇到右括号时进行操作,如果上一个字符是左括号,那么长度至少是2,具体还要看再前一个字符存储的长度。如果上一个是右括号,并且对应的位置(根据dp[i - 1]去找)是左括号,那么长度至少是dp[i - 1] + 2,具体还要看左括号的左边是否还有长度。

  78. 打家劫舍(一)

    1. 动态规划
      dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
  79. 打家劫舍(二)

    1. 动态规划
      分两种情况,从第一间开始偷,或者从第二间开始偷。
      从第一间开始偷dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
      从第二件开始偷dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i]);
  80. 买卖股票的最好时机(一)(只能买卖一次

    1. 动态规划
      int temp = pre[0];//第i - 1天买股票需要的钱
      pre[0] = max(pre[0], -prices[i]);//第i天买股票需要的钱
      pre[1] = max(pre[1], temp + prices[i]);//第i天卖股票后拥有的钱
    
  81. 买卖股票的最好时机(二)(可以多次购买

    1. 动态规划
      int temp = pre[0];//第i - 1天买股票剩下的钱
      pre[0] = max(pre[0], pre[1] - prices[i]);//第i天买股票剩下的钱
      pre[1] = max(pre[1], temp + prices[i]);//第i天买股票后拥有的钱
    
  82. 买卖股票的最好时机(三)(只能买卖两次

    1. 动态规划
      int temp = pre[0], temp2 = pre[1], temp3 = pre[2];
      pre[0] = max(pre[0], -prices[i]);    //第i天第一次买入股票
      pre[1] = max(pre[1], temp + prices[i]);    //第i天第一次卖出股票
      pre[2] = max(pre[2], temp2 - prices[i]);    //第i天第二次买入股票
      pre[3] = max(pre[3], temp3 + prices[i]);    //第i天第二次卖出股票
    
  83. 字符串变形

    1. 两次翻转
      第一次反转对其中每个字符串进行翻转,第二次对整体进行反转,挑其中一次进行大小写转换就可以了
  84. 最长公共前缀

    1. 逐个比较
  85. 验证IP地址

    1. 分开算细心点就可以了,开始要在末尾补上对应的符号进行判断。
  86. 大数加法

    1. 先反转两个字符串,然后相加,注意进位,然后再反转回来
  87. 合并两个有序的数组

    1. 从头部开始合并
    2. 从尾部开始合并
  88. 判断是否为回文字符串

    1. 双指针
  89. 合并区间

    1. 双指针
      先按照首区间进行升序排序,再拿intervals[i].start 和 ans.back().end进行比较
        if(intervals[i].start <= ans.back().end) {
            ans.back().end = max(intervals[i].end, ans.back().end);
        }
        else {
            ans.push_back(intervals[i]);
        }
    
  90. 最小覆盖子串

    1. 双指针
    string minWindow(string S, string T) {
            // write code here
            vector<int> hash(128, 0);
            for(auto ch : T) {
                ++hash[ch];
            }
            int need = T.size();
            int left = 0, right = 0;
            int minLen = INT_MAX;
            int minStart =  -1;
            int size = S.size();
            while(right < size) {
                if(hash[S[right]] > 0)//如果找到符合的字符,需求减一
                    --need;
                --hash[S[right]];//加入到滑动窗口,hash表减一
                ++right;//滑动窗口右扩
                while(need == 0) {//如果滑动窗口中字符串满足所有需求
                    if(right - left < minLen) {//如果比最小长度要小,则更新
                        minStart = left;
                        minLen = right - left;
                    }
                    if(++hash[S[left]] > 0) {//最重要的地方,如果滑动窗口内字符串缺少该字符就不满足要求,那么需求要加一
                        ++need;
                    }
                    ++left;//滑动窗口左边缩减
                }
            }
            return minStart == -1 ? "" : S.substr(minStart, minLen);
        }
    
  91. 反转字符串

    1. 双指针
      啪的一下就反转好了
  92. 最长无重复子数组

    1. 双指针,滑动窗口
      用哈希来判断有没有重复,重复的话滑动窗口一直左缩直到不重复为止,不重复了判断长度是否超过最大长度,超过则记录,滑动窗口右扩。
  93. 盛水最多的容器

    1. 双指针
      从两边往中间走,哪个小走哪个
  94. 接雨水问题

    1. 双指针
      每次都动小的那个,因为小的这边的水位有大的包围,一定不会更低,如果比较小值大,则更新较小值,否则添加水量
  95. 分糖果问题

    1. 贪心
      第一遍正序遍历,比前面大的都是前面的糖果数加一
      第二遍逆序遍历,比后面的大,则糖果数要么原本就大,要么是后面的糖果数加一
  96. 主持人调度(二)

    1. 最小堆
      首先将每个主持任务按开始时间和结束时间升序进行二次排序
      (1) 如果小根堆的堆顶小于等于当前任务的开始时间,说明做堆顶任务的主持人此时是空闲的,可以把当前主持任务揽下来,因此不需要增加主持人,把堆顶任务弹出,当前任务入堆;
      (2) 如果小根堆的堆顶大于当前任务的开始时间,这就说明这个最早能“收工”的主持人都没空接下当前任务,堆顶下面任务的主持人当然就更没空了,所以此时不出堆,新任务的结束时间直接入堆,表示需要增加一个主持人。
  97. 旋转数组

    1. 翻转三次
      翻转前半部分,翻转后半部分,全部翻转
  98. 螺旋矩阵

    1. 按圈输出
      真的做吐了,不想做了,就是很无脑的按圈遍历,注意边界情况
  99. 顺时针旋转矩阵

    1. 线性代数题
      temp[j][n-1-i] = mat[i][j];
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值