每日刷题计划Day[14,15]-递归

题源:LeetCode

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

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:
输入: n = 5, m = 3
输出: 3

示例 2:
输入: n = 10, m = 17
输出: 2

限制:
1 <= n <= 10^5
1 <= m <= 10^6

方法:递归
这个问题似乎有拆分为较小子问题的潜质:如果我们知道对于一个长度 n - 1 的序列,留下的是第几个元素,那么我们就可以由此计算出长度为 n 的序列的答案。
我们将上述问题建模为函数 f(n, m),该函数的返回值为最终留下的元素的序号(也就是下标)。
首先,长度为 n 的序列会先删除第 m % n 个元素,然后剩下一个长度为 n - 1 的序列。那么,我们可以递归地求解 f(n - 1, m),就可以知道对于剩下的 n - 1 个元素,最终会留下第几个元素,我们设答案为 x = f(n - 1, m)。
由于我们删除了第 m % n 个元素,将序列的长度变为 n - 1。当我们知道了 f(n - 1, m) 对应的答案 x 之后,我们也就可以知道,长度为 n 的序列最后一个删除的元素,应当是从 m % n 开始数的第 x 个元素。因此有 f(n, m) = (m % n + x) % n = (m + x) % n。

class Solution {
public:
    int lastRemaining(int n, int m) {
        int f = 0;
        for(int i = 2; i != n + 1; i++){
            f = (m + f) % i;
        }
        return f;
    }
};

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

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:
输入:head = [1,3,2]
输出:[2,3,1]

限制:
0 <= 链表长度 <= 10000

思路:可以拆解子问题,递归。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> res;
    int f(ListNode* p){
        if(p->next == NULL) return p->val;
        res.push_back(f(p->next));
        return p->val;
    }
    vector<int> reversePrint(ListNode* head) {
        if(head != NULL) {
            res.push_back(f(head));
        }
        return res;
    }
};

还可以用栈解。
这么看来,栈和递归有一些相似之处呢。
虽说函数的调用返回就是出栈入栈。但是用递归和栈做题的思想应该不是完全一致?还需要多做题再总结。

剑指 Offer 25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

限制:
0 <= 链表长度 <= 1000

思路:
递归。拆解子问题。
考虑结构体ListNode的next指向哪里。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1 == NULL) return l2;
        if(l2 == NULL) return l1;
        if(l1->val <= l2->val){
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }else{
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

还可以引入伪头节点遍历l1和l2。
时间复杂度都是O(M+N)
M是l1链长,N是l2链长。

50. Pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn )。

示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:
输入:x = 2.10000, n = 3
输出:9.26100

示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

提示:
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104

时空O(logn)的思路:
用递归实现分治法实现计算量减半。
考虑测试用例:

  • 测试x = 0, 1的情况
  • 测试n = 0, 1的情况
  • 测试n为负数的情况
  • 是否需要考虑溢出
  • 其他正常测试
class Solution {
public:
    double quickMul(double x, long long N) {
        if (N == 0) {
            return 1.0;
        }
        double y = quickMul(x, N / 2);
        return N % 2 == 0 ? y * y : y * y * x;
    }

    double myPow(double x, int n) {
        long long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }
};

时间O(logn)的思路:
用二进制拆分转变为迭代算法。

class Solution {
public:
    double quickMul(double x, long long N) {
        double ans = 1.0;
        // 贡献的初始值为 x
        double x_contribute = x;
        // 在对 N 进行二进制拆分的同时计算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二进制表示的最低位为 1,那么需要计入贡献
                ans *= x_contribute;
            }
            // 将贡献不断地平方
            x_contribute *= x_contribute;
            // 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
            N /= 2;
        }
        return ans;
    }

    double myPow(double x, int n) {
        long long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }
};

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

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:
输入:head = []
输出:[]

示例 3:
输入:head = [1]
输出:[1]

方法一:递归
递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。

如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == NULL || head->next == NULL) return head;
        ListNode* one = head;
        ListNode* two = one->next;
        ListNode* three = two->next;

        two->next = one;
        one->next = swapPairs(three);
        
        return two;
    }
};

233. 数字 1 的个数

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

示例 1:
输入:n = 13
输出:6

示例 2:
输入:n = 0
输出:0

提示:0 <= n <= 109

思路:
先想象有一个密码锁,一共有四位,每一位可单独滚动。
先锁住十位,强行让十位变成1,剩下三位可以随意滚动:XX1X。那么求十位出现1的个数,也就是,我可以滚出多少种密码组合,使得该密码小于等于n(注意十位被锁定成了1,转不动)。

假设 n = xyzdabc,此时我们求千位是 1 的个数,也就是 d 所在的位置。

那么此时有三种情况,

d == 0,那么千位上 1 的个数就是 xyz * 1000
d == 1,那么千位上 1 的个数就是 xyz * 1000 + abc + 1
d > 1,那么千位上 1 的个数就是 xyz * 1000 + 1000

为什么呢?

当我们考虑千位是 1 的时候,我们将千位定为 1,也就是 xyz1abc。
对于 xyz 的话,可以取 0,1,2…(xyz-1),也就是 xyz 种可能。
当 xyz 固定为上边其中的一个数的时候,abc 可以取 0,1,2…999,也就是 1000 种可能。
这样的话,总共就是 xyz*1000 种可能。

注意到,我们前三位只取到了 xyz-1,那么如果取 xyz 呢?

此时就出现了上边的三种情况,取决于 d 的值。
d == 1 的时候,千位刚好是 1,此时 abc 可以取的值就是 0 到 abc ,所以多加了 abc + 1。
d > 1 的时候,d 如果取 1,那么 abc 就可以取 0 到 999,此时就多加了 1000。

public int countDigitOne(int n) {
    int count = 0;
    //依次考虑个位、十位、百位...是 1
    //k = 1000, 对应于上边举的例子
    for (int k = 1; k <= n; k *= 10) { 
        // xyzdabc
        int abc = n % k;
        int xyzd = n / k;
        int d = xyzd % 10;
        int xyz = xyzd / 10;
        count += xyz * k;
        if (d > 1) {
            count += k;
        }
        if (d == 1) {
            count += abc + 1;
        }
        //如果不加这句的话,虽然 k 一直乘以 10,但由于溢出的问题
        //k 本来要大于 n 的时候,却小于了 n 会再次进入循环
        //此时代表最高位是 1 的情况也考虑完成了
        if(xyz == 0){
            break;
        }
    }
    return count;
}

数位dp
似懂非懂的一道题。
参考题解:https://leetcode-cn.com/problems/number-of-digit-one/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-50/

面试题 02.05. 链表求和

给定两个用链表表示的整数,每个节点包含一个数位。

这些数位是反向存放的,也就是个位排在链表首部。

编写函数对这两个整数求和,并用链表形式返回结果。

示例:
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912

进阶:思考一下,假设这些数位是正向存放的,又该如何解决呢?

示例:
输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出:9 -> 1 -> 2,即912

解题思路

  • 反向存放:
    从两个链表头开始相加,处理进位(单位之和大于10的问题)。创建新的链表节点。然后连接节点

  • 正向存放:
    利用栈先进后出,计算每一位的和,累加过程处理与反向一致。每一步处理连接节点摆放位置不同。

迭代做法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    //反向存放(本题)
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = new ListNode(-1);
        ListNode *cur = head;
        int carry = 0, sum = 0;
        while (l1 || l2 || carry) {
            sum = 0;
            if (l1 != NULL) {
                sum += l1->val;
                l1 = l1->next;
            }

            if (l2 != NULL) {
                sum += l2->val;
                l2 = l2->next;
            }

            sum += carry;
            ListNode *temp = new ListNode(sum % 10);
            carry = sum / 10;//进位
            cur->next = temp;
            cur = cur->next;
        }
        return head->next;
    }

    //正向存放
    ListNode* addInList(ListNode* head1, ListNode* head2) {
       stack<int> st1, st2;
        while (head1 || head2) {
            if (head1) {
                st1.push(head1->val);
                head1 = head1->next;
            }
            if (head2) {
                st2.push(head2->val);
                head2 = head2->next;
            }
        }
        ListNode *head = new ListNode(-1);
        int carry = 0, sum = 0;
        while (!st1.empty() || !st2.empty() || carry != 0) {
            sum = 0;
            if (!st1.empty()) {
                sum += st1.top();
                st1.pop();
            }
            if (!st2.empty()) {
                sum += st2.top();
                st2.pop();
            }
            sum += carry;
            ListNode *cur = new ListNode(sum % 10);
            carry = sum / 10;
            cur->next = head->next;
            head->next = cur;
        }
        return head->next;
    }
};

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:
输入:s = [“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

示例 2:
输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

提示:
1 <= s.length <= 105
s[i] 都是 ASCII 码表中的可打印字符

class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0, right = s.size() - 1;
        while(left < right)
            swap(s[left++], s[right--]);
    }
};

234. 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:
输入:head = [1,2,2,1]
输出:true

示例 2:
输入:head = [1,2]
输出:false

提示:
链表中节点数目在范围[1, 105] 内
0 <= Node.val <= 9

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

方法一:将值复制到数组中后用双指针法
复杂度分析
时间复杂度:O(n),其中 n 指的是链表的元素个数。
第一步: 遍历链表并将值复制到数组中,O(n)。
第二步:双指针判断是否为回文,执行了 O(n/2) 次的判断,即 O(n)。
总的时间复杂度:O(2n)=O(n)
空间复杂度:O(n),其中 n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。

方法二:递归

class Solution {
    ListNode* frontPointer;
public:
    bool recursivelyCheck(ListNode* currentNode) {
        if (currentNode != nullptr) {
            if (!recursivelyCheck(currentNode->next)) {
                return false;
            }
            if (currentNode->val != frontPointer->val) {
                return false;
            }
            frontPointer = frontPointer->next;
        }
        return true;
    }

    bool isPalindrome(ListNode* head) {
        frontPointer = head;
        return recursivelyCheck(head);
    }
};

方法三:快慢指针+反转链表
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
若链表有奇数个节点,则中间的节点应该看作是前半部分。
整个流程可以分为以下五个步骤:

  • 找到前半部分链表的尾节点。
  • 反转后半部分链表。
  • 判断是否回文。
  • 恢复链表。
  • 返回结果。
    该方法虽然可以将空间复杂度降到 O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。

复杂度分析
时间复杂度:O(n),其中 nnn 指的是链表的大小。
空间复杂度:O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)O(1)O(1)。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (head == nullptr) {
            return true;
        }

        // 找到前半部分链表的尾节点并反转后半部分链表
        ListNode* firstHalfEnd = endOfFirstHalf(head);
        ListNode* secondHalfStart = reverseList(firstHalfEnd->next);

        // 判断是否回文
        ListNode* p1 = head;
        ListNode* p2 = secondHalfStart;
        bool result = true;
        while (result && p2 != nullptr) {
            if (p1->val != p2->val) {
                result = false;
            }
            p1 = p1->next;
            p2 = p2->next;
        }        

        // 还原链表并返回结果
        firstHalfEnd->next = reverseList(secondHalfStart);
        return result;
    }

    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr != nullptr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    ListNode* endOfFirstHalf(ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast->next != nullptr && fast->next->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

224. 基本计算器

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

示例 1:
输入:s = “1 + 1”
输出:2

示例 2:
输入:s = " 2-1 + 2 "
输出:3

示例 3:
输入:s = “(1+(4+5+2)-3)+(6+8)”
输出:23

提示:
1 <= s.length <= 3 * 105
s 由数字、’+’、’-’、’(’、’)’、和 ’ ’ 组成
s 表示一个有效的表达式

思路:栈不存储计算结果,而是存储符号,1代表正号,-1代表负号,每次遇到标点符号的时候入栈。计算只有加减,所以直接在外围计算即可,不用进入栈中。

class Solution {
public:
    int calculate(string s) {
        stack<int> symbol;
        symbol.push(1);//初始为正号入栈
        int res = 0;
        int punctuation = 1;//符号
        for(int i = 0; i < s.length(); i++){
            if(s[i] == ' ') continue;
            else if(s[i] == '+')
            //更新符号,这一层的符号与自己和上一层的符号有关
                punctuation = symbol.top();
            else if(s[i] == '-')
            //更新符号,这一层的符号与自己和上一层的符号有关
                punctuation = -symbol.top();
            else if(s[i] == '(')
            //符号入栈
                symbol.push(punctuation);
            else if(s[i] == ')')
            //符号出栈,已经用完了,此时栈顶存放当前层的符号
                symbol.pop();
            else{//处理数字
                long num = 0;
                while(i < s.length() && s[i] >= '0' && s[i] <= '9'){
                    num = num * 10 + s[i] - '0';
                    i++;
                }
                i--;
                res += punctuation * num;
            }
        }
        return res;
    }
};

326. 3 的幂

给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。

整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x

示例 1:
输入:n = 27
输出:true

示例 2:
输入:n = 0
输出:false

示例 3:
输入:n = 9
输出:true

示例 4:
输入:n = 45
输出:false

提示:-231 <= n <= 231 - 1

class Solution {
    public boolean isPowerOfThree(int n) {
        if(n<=0) return false;
        while(n!=1){
            if(n%3!=0) return false;
            n/=3;
        }
        return n==1;
    }
}

44. 通配符匹配

给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。

‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

说明:

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。

示例 2:
输入:
s = “aa”
p = ""
输出: true
解释: '
’ 可以匹配任意字符串。

示例 3:
输入:
s = “cb”
p = “?a”
输出: false
解释: ‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。

示例 4:
输入:
s = “adceb”
p = “ab”
输出: true
解释: 第一个 ‘’ 可以匹配空字符串, 第二个 '’ 可以匹配字符串 “dce”.

示例 5:
输入:
s = “acdcb”
p = “a*c?b”
输出: false

本题与「10. 正则表达式匹配」非常类似,但相比较而言,本题稍微容易一些。因为在本题中,模式 pp 中的任意一个字符都是独立的,即不会和前后的字符互相关联,形成一个新的匹配模式。因此,本题的状态转移方程需要考虑的情况会少一些。

方法一:动态规划
在给定的模式 p 中,只会有三种类型的字符出现:

  • 小写字母 a-z,可以匹配对应的一个小写字母;

  • 问号 ?,可以匹配任意一个小写字母;

  • 星号 *,可以匹配任意字符串,可以为空,也就是匹配零或任意多个小写字母。

dp[i][j]表示字符串s前i个字符和模式p的前j个字符是否能匹配。在进行状态转移时,我们可以考虑模式p的第j个字符pj,与之对应的是字符串s中的dii个元素si:
- pj是小写字母,那么si必须也为相同的小写字母,状态转移方程为:dp[i][j] = (si与pj相同) && dp[i-1][j-1]
- 如果pj是问号,那么对si没有任何要求,状态转移方程为:dp[i][j] = dp[i - 1][j - 1]
- 如果pi是星号,那么同样对si没有任何要求,但是星号可以匹配零或任意多个小写字母,因此状态转移方程分为两种情况,即使用或不使用这个星号:
dp[i][j] = dp[i][j - 1] || dp[i - 1][j]请添加图片描述
边界条件:dp[0][j]和dp[i][0],涉及到空字符串或空模式的情况。

  • dp[0][0] = true,当字符串s和模式p均为空时,匹配成功
  • dp[i][0] = false,空模式无法匹配非空字符串。
  • dp[0][j]需要分情况讨论:因为星号才能匹配空字符串,所以只有当模式p的前j个字符均为星号时,dp[0][j]才为真。

可以发现,dp[i][0] 的值恒为假,dp[0][j] 在 j 大于模式 p 的开头出现的星号字符个数之后,值也恒为假,而 dp[i][j] 的默认值(其它情况)也为假,因此在对动态规划的数组初始化时,我们就可以将所有的状态初始化为 false,减少状态转移的代码编写难度。

最终的答案即为 dp[m][n],其中 m 和 n 分别是字符串 s 和模式 p 的长度。需要注意的是,由于大部分语言中字符串的下标从 0 开始,因此 si和pj分别对应着s[i - 1]和p[j - 1]。

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size();
        int n = p.size();
        vector<vector<int> > dp(m + 1, vector<int>(n+1));
        dp[0][0] = true;
        for(int i = 1;i <= n; i++){
            if(p[i - 1] == '*'){
                dp[0][i] = true;
            }else break;
        }

        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(p[j - 1] == '*')
                    dp[i][j] = dp[i][j-1] || dp[i - 1][j];
                else if(p[j - 1] == '?' || s[i - 1] == p[j - 1])
                dp[i][j] = dp[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }
};

时间复杂度:O(mn),其中 m 和 n 分别是字符串 s 和模式 p 的长度。

空间复杂度:O(mn),即为存储所有 (m+1)(n+1) 个状态需要的空间。此外,在状态转移方程中,由于 dp[i][j] 只会从 dp[i][…] 以及 dp[i−1][…] 转移而来,因此我们可以使用滚动数组对空间进行优化,即用两个长度为 n+1 的一维数组代替整个二维数组进行状态转移,空间复杂度为 O(n)。

tips: & | 按位与或。

方法二:贪心(先放放)

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

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:
5
/
2 6
/
1 3

示例 1:
输入: [1,6,3,2,5]
输出: false

示例 2:
输入: [1,3,2,6,5]
输出: true

提示:
数组长度 <= 1000

思路:
二叉搜索树的特点是:左子树所有结点比当前节点值小,右子树所有结点比当前结点值大,由于后序遍历根结点在最后访问,因此可以递归地判断。


class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        return helper(postorder, 0, postorder.size() - 1);
    }

    bool helper(vector<int>& postorder, int l, int r){
        if(l >= r) return true;//当前子树为空树或者只有一个结点那么肯定符合要求

        int divide = r - 1;
        while(divide >= l){//找到右子树的所有节点
        //右子树所有结点比当前结点值大postorder[divide] > postorder[r]
        //后序遍历最后一个节点时根节点,所以postorder[r]是根节点
            if(postorder[divide] < postorder[r]) break;
            divide--;
        }
        for(int i = l; i <= divide; i++){
            //如果剩下的左子树中有大于当前子树根节点值的结点直接返回false
            if(postorder[i] > postorder[r]) return false;
        }
        return helper(postorder, l, divide) && helper(postorder, divide + 1, r - 1);
        //递归地判断左右子树
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值