算法刷刷刷(一、递归)

每天努力多一点,打开刷题,谁tm过情人节啊。
有什么比把快乐带给别人更快乐的呢hahaha
递归别扯三部曲,没有用,比如:什么条件返回应该是本能,而不是靠背过程去找。

递归

  • 一、简单题的集合
    • 1. [21. 合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)
      • 理解
      • 代码:
    • 2.[203. 移除链表元素](https://leetcode-cn.com/problems/remove-linked-list-elements/)
      • 理解
      • 代码
    • 3.[206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)
      • 理解
      • 代码
    • 4.[231. 2 的幂](https://leetcode-cn.com/problems/power-of-two/)
      • 理解
      • 代码
    • 5.[234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/)
      • 理解
      • 代码
    • 6.[509. 斐波那契数](https://leetcode-cn.com/problems/fibonacci-number/)
      • 理解
      • 代码
    • 7.[剑指 Offer 06. 从尾到头打印链表](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/)
      • 理解
      • 代码
    • 8.[剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/)
      • 理解
      • 代码
    • 9.[面试题 08.06. 汉诺塔问题](https://leetcode-cn.com/problems/hanota-lcci/)
      • 理解
      • 代码
    • 10.[2. 两数相加](https://leetcode-cn.com/problems/add-two-numbers/)
      • 理解
      • 代码
    • 11.[24. 两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/)
      • 理解
      • 代码
    • 12.[50. Pow(x, n)](https://leetcode-cn.com/problems/powx-n/)
      • 理解
      • 代码
    • 13.[143. 重排链表](https://leetcode-cn.com/problems/reorder-list/)
      • 理解
      • 代码
    • 14.[241. 为运算表达式设计优先级](https://leetcode-cn.com/problems/different-ways-to-add-parentheses/)
      • 理解
      • 代码
    • 15.[394. 字符串解码](https://leetcode-cn.com/problems/decode-string/)
      • 理解
      • 代码
    • 16.[486. 预测赢家](https://leetcode-cn.com/problems/predict-the-winner/)
      • 理解
      • 代码
    • 17.[779. 第K个语法符号](https://leetcode-cn.com/problems/k-th-symbol-in-grammar/)
      • 理解
      • 代码
    • 18.[894. 所有可能的满二叉树](https://leetcode-cn.com/problems/all-possible-full-binary-trees/)
      • 理解
      • 代码
    • 19.[1969. 数组元素的最小非零乘积](https://leetcode-cn.com/problems/minimum-non-zero-product-of-the-array-elements/)
      • 理解
      • 代码
    • 20.[面试题 08.05. 递归乘法](https://leetcode-cn.com/problems/recursive-mulitply-lcci/)
      • 理解
      • 代码
    • 21.[剑指 Offer 64. 求1+2+…+n](https://leetcode-cn.com/problems/qiu-12n-lcof/)
      • 理解
      • 代码
    • [22.剑指 Offer 33. 二叉搜索树的后序遍历序列](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/)
      • 理解
      • 代码

一、简单题的集合

1. 21. 合并两个有序链表

理解

这个题,先抽出一个最简单的动作,就是每次找一个最小的数。

给个例子:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

1.我们要做的是排序+存储
1.先进行从小到大排序,[1,1,2,3,4,4],排完一个位置就把这个位置放入一个类似栈的地方,先放着,等完全排完,再进行存储。
2.现在全部进栈了,先拿出4,再拿下一个4,就是这种。

代码:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr) 
            return list2;
        else if(list2 == nullptr) 
            return list1; 
        else if(list1->val < list2->val) {
            list1->next = mergeTwoLists(list1->next,list2);
            return list1;
        }
        else {
            list2->next = mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

2.203. 移除链表元素

理解

凡是一个while能完成的,理论上递归都可以,所以这道题,so easy。
只稍微说一下这个指针的问题,比如{1,2,3,3,4,5,6}删除的数为val = 4,到了下标4这里指针的next为{5,6},如果node->val == val那么就返回{5,6}给下标3的指针,如果node->val != val 那么就返回{4,5,6}这不就没删除嘛。(我言语不清晰haha)

代码

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
     if(head == nullptr) return NULL;
     head->next = removeElements(head->next,val);
     return head->val == val?head->next:head;
    }
};

3.206. 反转链表

理解

类似上一道题1->2->3->4->5->首先5空过,4与5对调1->2->3->4<-5,3和4对调,1->2->3<-4<-5,如此往复最后变成1<-2<-3<-4<-5。
是不是挺简单的,反正递归进行的时候每一个栈已经找到了每一个结点(我不会说哈,描述有大问题,我去评论区找了给个大佬的描述作者:NoColor96)

代码

class Solution {
/*
            第一轮出栈,head为5,head.next为空,返回5
            第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4,
                      把当前节点的子节点的子节点指向当前节点
                      此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null
                      此时链表为1->2->3->4<-5
                      返回节点5
            第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3,
                      此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null
                      此时链表为1->2->3<-4<-5
                      返回节点5
            第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2,
                      此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null
                      此时链表为1->2<-3<-4<-5
                      返回节点5
            第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1,
                      此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null
                      此时链表为1<-2<-3<-4<-5
                      返回节点5
            出栈完成,最终头节点5->4->3-2->1
         */
public:
    ListNode* reverseList(ListNode* head) {
     if(head == nullptr || head->next == nullptr) return head;
     ListNode* dududu = reverseList(head->next);
     head->next->next = head;
     head->next = nullptr;
     return dududu;
    }
};

4.231. 2 的幂

理解

‍‍‍有手就行吧。

代码

class Solution {
public:
    bool isPowerOfTwo(int n) {
       if(n == 0) return false;
       else if(n == 1) return true;
       else if(n%2 == 1) return false;
       return isPowerOfTwo(n/2);
    }
};

一模一样的题型:

  1. 326. 3 的幂
class Solution {
public:
    bool isPowerOfThree(int n) {
     if(n==0) return false;
     else if(n == 1) return true;
     else if(n%3 == 1||n%3==2) return false;
     return isPowerOfThree(n/3);
    }
};

2.342. 4的幂

class Solution {
public:
    bool isPowerOfFour(int n) {
        if(n==0) return false;
        else if(n==1) return true;
        else if(n%4 == 1 || n%4 == 2||n%4==3) return false;
        return isPowerOfFour(n/4);
    }
};

5.234. 回文链表

理解

递归可以让链表倒着输出,所以也可以让链表倒着比较。这答案这不手到擒来。
原理就是先用递归把跑到尾,然后从尾一个个退出来,同时头一个个向后走。

代码

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* end = head;
        return equle_head_end(end,head);
    }
    bool equle_head_end(ListNode* end,ListNode* &head){
        if(end == nullptr) return true; //这个条件是出递归的条件,我们放入递归使用的是end,所以也是end当作出递归的条件。
        bool answer = equle_head_end(end->next,head);
        answer = answer && end->val == head->val;
        head  = head->next;
        return answer;
    }
};

6.509. 斐波那契数

理解

很经典。

代码

class Solution {
public:
    int fib(int n) {
        if(n < 2) return n;
        return fib(n-1) + fib(n-2);
    }
};

7.剑指 Offer 06. 从尾到头打印链表

理解

是倒着输出的题哦。

代码

class Solution {
public:
    vector<int> answer;
    vector<int> reversePrint(ListNode* head) {
        if(head == nullptr) return answer;
        reversePrint(head->next);
        answer.push_back(head->val);
        return answer;
    }
};

8.剑指 Offer 62. 圆圈中最后剩下的数字

理解

题挺难的,打个比方:
`{A,B,C,D,E},每次死第3个.

  1. 第一次死的是C,剩下{A,B,D,E}。
  2. 第二次死的是A,剩下的是{B,D,E}。
  3. 第三次死的是E,剩下的是{B,D}。
  4. 第四次死的是B,剩下的是{D}。
    我已经把最后活下来的加粗了,我们可以看到,最后存在的在最后的时刻的位置永远是0(对数组来说).。
    是的我们需要倒着推:
  5. 第四次死的是B,加回去,省下变成了{D,B}然后右移动3次,变成{B,
  6. 第三次死的是E,加回去变成{B,,E},移动三次变成{B,,E}
  7. 第二次死的是A,加回去变成{B,,E,A},移动三次变成{,E,A,B}
  8. 第一次死的是C,加回去变成{B,,E,AC},移动三次变成{A,B,C,,E}

康康这个D,每次就是跟着加3除当前总数啊,所以答案显而易见啦。

代码

class Solution {
public:
    int lastRemaining(int n, int m) {
        if(n == 1) return 0;
        int answer = (lastRemaining(n-1,m) + m)%n;
        return answer;
    }
};

类似题目:

  1. 1823. 找出游戏的获胜者
    代码:
class Solution {
public:
    int become(int n,int k){
        return n==1?0:(become(n-1,k)+k)%n;
    }
    int findTheWinner(int n, int k) {
        return become(n,k)+1;
    }
};

9.面试题 08.06. 汉诺塔问题

理解

这是一个憨批递归题呢,其实直接C = A直接可以出结果,haha
其实这个题有技术含量的。
我说一下大家对于递归可能就是精通了。
咱们先找一个问题的最简单的解决方法,比如把A的前n-1个放入B,然后把第n个放入C,然后把B中的放入C就好了。 小问题找到了,小问题还要实现的是A的前n-1个放入B,B中放入C。 我们惊奇的发现小问题想要实现的是另外一个类似的小问题哎,这样就等于重复的做一个工作啦,好耶!!!这就是递归啦。

代码

class Solution {
public:
    void move(vector<int>& start, vector<int>& free, vector<int>& target,int n){
    if(n  == 1) {
        target.push_back(start.back());
        start.pop_back();
        return;
    }
    //A前n-1个放入B
    move(start,target,free,n-1);
    //A第n个放入C
    move(start,free,target,1);
    //B的n-1个放入C
    move(free,start,target,n-1);
    }
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
      move(A,B,C,A.size());
    }
};

10.2. 两数相加

理解

无非就是十进制加减法,因为他数是倒着的,所以用递归先算最后一个数。

代码

class Solution {
public:
    ListNode* sum(ListNode* l1, ListNode* l2,int n){
        
        if(l1 == nullptr && l2 == nullptr && n !=0){
            ListNode* k = new ListNode(n);
            return k;
        }
        else if(l1 == nullptr && l2 == nullptr) return l1;
        else if(l1 == nullptr && l2 != nullptr){
            ListNode* k = new ListNode(l2->val);
            l1 = k;
            l1->val = l1->val + n;
            n = l1->val/10;
            l1->val = l1->val %10;
            l1->next = sum(l1->next,l2->next,n);
            return l1;
        }
        else if(l1 !=nullptr && l2 == nullptr){
            l1->val = l1->val + n;
            n = l1->val/10;
            l1->val = l1->val %10;
            l1->next = sum(l1->next,l2,n);
            return l1;
        }
        else {
            l1->val +=l2->val;
            l1->val = l1->val + n;
            n = l1->val/10;
            l1->val = l1->val %10;
            l1->next = sum(l1->next,l2->next,n);
            return l1;
        }
        
    }
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        l1 = sum(l1,l2,0);
        return l1;
    }
};

11.24. 两两交换链表中的节点

理解

很简单的mid,助我早点摸鱼的题haha

代码

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;
        ListNode* temp = head->next;
        head->next = swapPairs(head->next->next);
        ListNode* temp1 = head;
        head = temp;
        head->next = temp1;
        return head;
    }
};

12.50. Pow(x, n)

理解

这个题难点不在于递归,而在于愚蠢的样例。用一个快速幂的小技巧,就是25 = 21*22*22,26=23*23
建议这个题能理解理解,理解不了背过。

代码

class Solution {
public:
    double myPow(double x, int n) {
        if(n == 0) return 1.0;
        else if(n == -1) return 1/x;
        else if(n == 1) return x;
        double divisor = myPow(x,n/2);
        double remainder = myPow(x,n%2);
        return divisor*divisor*remainder;
    }
};

类似题型:

  1. 1922. 统计好数字的数目
class Solution {
public:
    long long mod = (int)1e9+7;
    int countGoodNumbers(long long n) {
     return int(pow(5,(n+1)/2)*pow(4,n/2)%mod);
    }
    long long  pow(int x,long long n){
        if(n == 0) return 1;
        else if(n == 1) return x;
        if(n%2 == 1) return (x*pow(x,n-1))%mod;
        long long half = pow(x,n/2);
        return half*half%mod;
    }
};

13.143. 重排链表

理解

这道题这个递归就是逊啦,我的思路是用递归弄一个从后向前排列,另外一个链从前往后排列,每次递归都把最后一个摘下来,插了前面,用i跟j判断是到中间了。(用递归实现慢很正常)

代码

class Solution {
public:
    void reorderList(ListNode* head) {
      int i = 0;
      int j = -1;
      ListNode* more = head;
      while(more){
          j++;
          more = more->next;
      }
      recursive(head,head,i,j);
    }
    void recursive(ListNode* &start,ListNode* end,int &i,int &j){
        if(end == nullptr || end -> next == nullptr) return;
        recursive(start,end -> next,i,j);
        if(i >= j) return ;
        i++;
        j--;
        ListNode* temp = end->next;
        end->next = nullptr;
        temp->next = start->next;
        start->next = temp;
        start = temp->next;
    }
};

14.241. 为运算表达式设计优先级

理解

有分治的思想。递归每次是到了符号左右分两边看,最后是纯数字的时候再开始出栈。

代码

class Solution {
public:
    vector<int> diffWaysToCompute(string expression) {
     vector<int> temp;
     for(int i = 0;i<expression.length();i++){
         if(expression[i] == '+' ||expression[i] == '-'||expression[i] == '*'){
             vector<int> left = diffWaysToCompute(expression.substr(0,i));
             vector<int> right = diffWaysToCompute(expression.substr(i+1));
            for(auto l:left)
            for(auto r:right){
             switch(expression[i]){
                 case '+':temp.push_back(l+r);break;
                 case '-':temp.push_back(l-r);break;
                 case '*':temp.push_back(l*r);break;
             }
            }
         }
     }
     if(temp.empty()) temp.push_back(stoi(expression));
     return temp;
    }
};

15.394. 字符串解码

理解

死抓一个点,到[进入递归,到]出递归。

代码

class Solution {
public:
  string decodeString(string s){
   string answer="";stack<char> d;int num;
   for(int i = 0;i<s.length();i++){
       if(s[i]>='a'&&s[i]<='z') answer+=s[i];
       else if(s[i] >='0' && s[i]<='9'){
           int j = i,num = 0 ;
           while(s[j] >='0' && s[j]<='9') j++;
           num = stoi(s.substr(i,j-i));i = j-1;
       }
       else if(s[i] == '['){
           d.push('[');
           string ddd = decodeString(s.substr(i+1));
           for(int l = 0;l<num-1;l++)  answer +=ddd;
       }
       else if(s[i] == ']')
           if(d.empty()) return answer; 
           else d.pop();       
   }
   return answer;
    }
};

16.486. 预测赢家

理解

其实这是道dp,不过我的博客是个递归专题,也就不写记忆啊啥的。因为B每次都选最利于自己的结果,所以当b选择的时候一定要A必胜才能返回true所以用&&,A选择的时候有一个能赢就行,所以用||。

代码

class Solution {
public:
    bool buibuibui(vector<int> nums,int left,int right,int game1,int game2,bool num){
        if(left > right) return game1 >= game2;
        if(num)
            return buibuibui(nums,left+1,right,game1+nums[left],game2,0) || 
                   buibuibui(nums,left,right-1,game1+nums[right],game2,0);
        else
            return buibuibui(nums,left+1,right,game1,game2+nums[left],1) && 
                   buibuibui(nums,left,right-1,game1,game2+nums[right],1);
    }
    bool PredictTheWinner(vector<int>& nums) {
        return buibuibui(nums,0,nums.size() - 1,0,0,1);
    }
};

17.779. 第K个语法符号

理解

这题毛毛雨啦,有手就行。可以看到第n行是第n-1行加上第n-1行的逆置组成。
那就好办了,递归到第二行,01一判断就好啦。

代码

class Solution {
public:
    int kthGrammar(int n, int k) {
        if(n == 1) return 0;
        int length = 2<<n-1;
        if(k > length/2) return kthGrammar(n-1,k - length/2) == 0 ? 1:0;
        else return kthGrammar(n-1,k);
    }
};

类似的题:

  1. 1545. 找出第 N 个二进制字符串中的第 K 位
    代码:
class Solution {
public:
    char findKthBit(int n, int k) {
     if(n==1) return '0';
     int mid = 1<<n-1;
     if(k == mid) return '1';
     else if(k > mid) return findKthBit(n-1,2*mid - k) == '0'?'1':'0';
     else return findKthBit(n-1,k);
    }
};

18.894. 所有可能的满二叉树

理解

递归一定要考虑的是大问题怎么分解,不是上来想分解以后怎么做,先分解大问题,大问题分解完再想小问题。
树是天生可递归的结构,大家如果某些题想用递归可以先画树。

代码

class Solution {
public:
    vector<TreeNode*> allPossibleFBT(int n) {
       vector<TreeNode*> answer;
       if(n == 1){
           TreeNode* node = new TreeNode(0);
           answer.push_back(node);
       }
       else if(n%2==0) return answer;
       for(int i = 1;i< n;i = i+2)
           for(TreeNode* l : allPossibleFBT(i))
           for(TreeNode* r: allPossibleFBT(n - 1 - i)){
               TreeNode* node = new TreeNode(0,l,r);
               answer.push_back(node);
           }
       return answer;
    }
};

19.1969. 数组元素的最小非零乘积

理解

首先这个题被各位大数学家证明,一定会出[1111,0001,1110。。。]这样的数据。
我就不给证明了,就简单说一下,让大家理解(市面上的垃圾书就是给大家简单说说,从来不给证明,也就是说我就是垃圾haha)
比如:p = 3
那么结果有:[111,110,101,100,011,010,001]-》
格局放大,除了111剩下的最低位有3个1第二位3个1,最大位有3个1,最小的数是001所以是由3个001与3个110组成。
公式:
在这里插入图片描述
补充一句,递归过不了你tm就别写了递归题里。(也可能我菜),大家可以写一下递归试试。

代码

class Solution {
public:
    int mod = (int)1e9+7;
    int minNonZeroProduct(int p) {
     long long max = ((1LL << p) - 1) % mod ;
     long long n = (1LL <<(p - 1)) - 1;
     long long a = ((1LL << p) - 2) % mod;
     for(;n;n >>= 1){
            if(n & 1) max = max * a % mod;
            a = a * a % mod;
     }
     return max;
    }
};

20.面试题 08.05. 递归乘法

理解

都刷到这里了,这道题不有手就行吗。

代码

class Solution {
public:
    int multiply(int A, int B) {
     if(A == 0) return 0;
     else if(A == 1) return B;
     else if(A==-1) return -B;
     int half = multiply(A/2,B);
     int rect = multiply(A%2,B);
     return half + half + rect;
    }
};

21.剑指 Offer 64. 求1+2+…+n

理解

别不能用递归的题放了递归分类好吗?不过,小学生题吧。

代码

class Solution {
public:
    int sumNums(int n) {
     return n*(n+1)/2;
    }
};

网友acvv_菜的做法:

class Solution {
public:
    int sumNums(int n) {
      bool a[n][n+1];
     return sizeof(a)>>1;
    }
};

22.剑指 Offer 33. 二叉搜索树的后序遍历序列

理解

树是天生的可递归的,模拟树去做就行,这个左大右小,后是左右根。咱们只要确定左<根,右>根就好了

代码

class Solution {
public:
    bool dd(vector<int> postorder,int left,int right){
        if(left >= right) return true;
        int m = left;
        while(postorder[right] > postorder[m]) m++;
        int n =m;
        while(postorder[m] > postorder[right]) m++;
        return m == right && dd(postorder,n,right-1) && dd(postorder,left,n-1);

    }
    bool verifyPostorder(vector<int>& postorder) {
        return dd(postorder,0,postorder.size() - 1);
    }
};

困难题不定时更新毕竟很多都是脑筋急转弯,挺耗时间的。

你可能感兴趣的:(C++学习,算法,链表,leetcode)