那些常见的算法“面筋”II

买卖股票的最佳时机(贪心 / DP)

最长回文子串(中心扩散法)

二叉树的锯齿形层序遍历(层序遍历+记忆)

二叉树的最近公共祖先(DFS)

全排列(简单记忆+搜索)

一 买卖股票的最佳时机

        解决方法:贪心 / 动态规划

        但对一次性买卖,其实两者考虑思路差不多。

        贪心——随区间左端点固定,右端点开始遍历移动,确定带当前区间的最小值,同时比较每次:右端点值-当前最小值 的最大值

public int maxProfit(int[] prices) {
    int res = 0;
    int miv = Integer.MAX_VALUE;
    for (int i = 0; i < prices.length; i++) {
        miv = Math.min(miv, prices[i]);
        res = Math.max(res, prices[i] - miv);
    }   
    return res;     
}

        动态规划(其实思想与贪心一致(低买高卖)但是动态规划的格式)

dp[i][0]表示第i天的持有   = (第i-1天持有)dp[i-1][0] 或 (第i-1天不持有+第i天买入) -prices[i]

dp[i][1]表示第i天的不持有 = (第i-1天不持有)dp[i-1][1] 或 (第i-1天持有+第i天卖出) dp[i-1][0]+price[i]

显然dp[0]只能从dp[0]推出,因为只能买卖一次。

public int maxProfit(int[] prices) {
    int[][] dp = new int[prices.length + 1][2];
    dp[1][0] = -prices[0];  // 初始化,不如最小必然都是0
    for (int i = 2; i <= prices.length; i++) {
        dp[i][0] = Math.max(dp[i-1][0], -prices[i-1]);  // 注意负号,此时买得少对应max
        dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i-1]);
    }
    return dp[prices.length][1];     
}

        可以优化为一维,很简单:

public int maxProfit(int[] prices) {
    int[] dp = new int[2];
    dp[0] = -prices[0];  // 初始化,不如最小必然都是0
    for (int i = 2; i <= prices.length; i++) {
        dp[0] = Math.max(dp[0], -prices[i-1]);  // 注意负号,此时买得少对应max
        dp[1] = Math.max(dp[1], dp[0] + prices[i-1]);
    }
    return dp[1];     
}

二 最长回文子串

        这里采用中心扩散法,思路非常之节点,时间复杂度O(n^2)力扣上的时间空间都跑90%。

        思路:从头节点遍历到尾节点的上一个节点(因为最糟糕的情况也是一个字母,所以不需要遍历到尾节点),然后把当前节点当做回文子串中心向两边扩散,或把当前节点和当前节点的下一个节点当回文子串向两边扩散,记录最长的边界差值,然后记录到两个位置上,以便最后读取回文子串。

class Solution {
    public String longestPalindrome(String s) {
        int start = 0;
        int end = 0;
        for (int i = 0; i < s.length() - 1; i++) {
            int len1 = expandAroundCenter(s, i, i);  // 奇数连续子串的情况
            int len2 = expandAroundCenter(s, i, i + 1);  // 偶数连续子串的情况
            int len = Math.max(len1, len2);  
            if (len > end - start) {
                start = i - (len - 1) / 2;  // len就是当前节点最长子串的长度
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);  // 左闭右开读取
    }

    // 按left right同时向两边扩散,以判断是否回文(注意边界)
    public int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            --left;
            ++right;
        }
        return right - left - 1;
    }
}

三 二叉树的锯齿形层序遍历

        非常之简单,其实就是二叉树的层序遍历加个每层布尔值判断。

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Deque<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        boolean bo = true;  // true代表左往右,false代表右往左
        while (!queue.isEmpty()) {
            List<Integer> list = new ArrayList<>();
            int curSize = queue.size();
            while (curSize-- > 0) {
                if (bo) {
                    TreeNode node = queue.pollFirst();
                    list.add(node.val);
                    if (node.left != null) queue.add(node.left);
                    if (node.right != null) queue.add(node.right);
                } else {
                    TreeNode node = queue.pollLast();
                    list.add(node.val);
                    if (node.right != null) queue.addFirst(node.right);
                    if (node.left != null) queue.addFirst(node.left);
                }    
            }
            res.add(list);
            bo = !bo;
        }
        return res;
    }
}

四 二叉树的最近公共祖先

        用DFS,以后序遍历的方式从底至顶找根节点比较。注意一个重要的条件:节点值唯一。

        思路:深度搜索从底至顶搜索,用两个布尔值记录是否找到节点P/Q,若有一个节点找到,那么该递归层就会返回true,如果遇到另一个递归层也返回true,那么就已找到root,后续的另一边迭代层不可能再有true,因为p/q值唯一。如果刚好在迭代返回true时,p/q之一巧好在某一根节点,那么也找到root。每次迭代的布尔值即为当前节点值是否为p/q值或是否已经访问过p/q值。

class Solution {
    TreeNode res = null; 
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root, p, q);
        return res;
    }
    public boolean dfs(TreeNode root, TreeNode p, TreeNode q) { 
        if (root == null) return false;  // 有点后序遍历的意思,遍历到最底层开始找
        boolean bol = dfs(root.left, p, q);
        boolean bor = dfs(root.right, p, q);
        // 前者针对已经找到两边为TRUE,后者针对根节点是p/q之一,而且注意所有节点的值是惟一的,pq不能相同
        // 后者并不冲突,一旦两边同时达到,那么肯定是p/q根节点重合
        if ((bol && bor) || ((root.val == p.val || root.val == q.val) && (bol || bor))) {
            res = root;
        }

        // 找到p/q或前面已经找到p/q则返回true
        return root.val == p.val || root.val == q.val || bol || bor;
    }
}

五 全排列

        存思路型的全排列,即用按for循环遍历,然后用Set或布尔值数组记录是否访问即可,思路清晰简单,速度能在85%左右,over,存储来说,布尔值数组会底一些,建议后者。

class Solution {
    List<List<Integer>> res;
    HashSet<Integer> set;  // 两种方法,set判断或布尔值数组bo[]判断
    List<Integer> list;
    public List<List<Integer>> permute(int[] nums) {
        res = new ArrayList<>();
        set = new HashSet<>();
        list = new ArrayList<>();
        dfs(nums);
        return res;
    }

    private void dfs(int[] nums) {
        if (list.size() == nums.length) {
            List<Integer> result = new ArrayList<>();
            for (Integer i : list) result.add(i);
            res.add(result);
        }
        for (int i = 0; i < nums.length; i++) {
            if (set.contains(nums[i])) continue;
            list.add(nums[i]);
            set.add(nums[i]);  // bo[i] = true
            dfs(nums);
            list.remove(list.size() - 1);
            set.remove(nums[i]);  // bo[i] = false
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值