前言

由于本人一边要实习,一边要上课,最近刷题也是零零散散。一直都是按 LeetCode 官方默认顺序刷,一段时间刷下来,认为效率略低或者成长性比较低下,还是少了对思路的解析和总结,决定今天开始按 Tag 刷,也是便于自己总结和温故,一天一天先给文章贴上题目,总结日后再补上。愿每个努力的人,都不悔当初。好了,开始切入正题

Next Permutation (Medium)

问题描述

题目还是之前的 排列组合, 不同的是,并不能将所有结果都推导一遍,然后再挑选,而是给出你排列组合中的某一位置,要求直接推导出下一位置。

Examples
Input
[1, 2, 5, 4, 3]
Output
[1, 3, 2, 4, 5]

算法思路

也让我想得有点晕,总结起来,这类题要按照 顺着平常的思维方式,将逻辑给理清楚,思路就相当清晰了

回想下我们是怎么排列组合的,在这里我们 按状态 把数组分为两队,我们认为 前一队是一定的后一队是完全排列过的,这种情况下,在前一队保持不动的前提下,后一队已经达到排列组合的最后一个状态(降序),这时,我们需要从后一队中挑选出一个,比前一队最后一个数大一级的数,并和它交换,换句话说,前一队进入下一状态,后一队重置为第一状态(升序),也就是反转数组

1234 5 -> 1235 4
123 54 -> 124 35
1243 5 -> 1245 3
124 53 -> 125 34
1253 4 -> 1254 3
12 543 -> 13 245
......
5431 2 -> 5432 1

也就是相当于,在不采用回溯递归思想的前提下,靠人工逻辑来实现算法

AC 代码

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int len = nums.size();
        if (len < 2) {
            return;
        }
        // find the cut of two sides
        int cut = len - 1;
        while (cut > 0) {
            if (nums[cut - 1] < nums[cut]) {
                break;
            }
            cut--;
        }
        if (cut == 0) {
            reverse(nums, 0, len - 1);
        } else {
            int upperClass = len - 1;
            // find a upperClass class than the end of the first side
            while (upperClass >= cut) {
                if (nums[upperClass] > nums[cut - 1]) {
                    break;
                }
                upperClass--;
            }
            // change to a new arrangement
            swap(nums, upperClass, cut - 1);
            // reset the last side
            reverse(nums, cut, len - 1);
        }
    }
    
    void swap(vector<int>& nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
    
    void reverse(vector<int>& nums, int start, int end) {
        for (int i = start; i <= (end + start) / 2; i++) {
            swap(nums, i, start + end - i);
        }
    }
};

Maximum Product Subarray (Medium)

问题描述

最大子数组之积

Examples
Input
[3, -2, 3, 0, -2, -4]
Output
8

方法一

算法思路

一开始本来想用 贪心,但遍历时,结果如果出现负数,并不会知道后面是否还有成对的负数让结果变正。如果给你一个这样的数组,按照人脑的思考习惯,你肯定会先把数组从头到尾看一遍,找出成队的负数。根据这个启示,我的思路如下展开

  • 首先标记 第一个负数最后一个负数

  • 若负数 全成对,最大值肯定是 全部之积

  • 若负数 单一个,要达到最大乘积,要么把 最左边的负数 抛掉,要么把 最右边的负数 抛掉

  • 碰到零便 递归

AC 代码

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int len = nums.size();
        int MAX = 0x80000000;
        int startNeg = 0, endNeg = len - 1;
        bool firstNeg = true;
        int negCount = 0;
        // find indeces of the first and last negtive
        // start recursion when meets zero and refresh the length
        for (int i = 0; i < len; i++) {
            if (nums[i] == 0) {
                // zero
                vector<int> vec(nums.begin() + i + 1, nums.end());
                int temp = maxProduct(vec);
                MAX = max(MAX, temp);
                len = i;
                break;
            } else if (nums[i] < 0) {
                // negtive
                negCount++;
                if (firstNeg) {
                    startNeg = i;
                    firstNeg = false;
                }
                endNeg = i;
            }
        }
        if (len <= 0) {
            // it explains that the vector is cut by zero
            // so MAX may be 0
            MAX = max(MAX, 0);
        } else if (len == 1) {
            // avoid overflow
            MAX = max(MAX, nums[0]);
        } else if (negCount % 2 == 0) {
            // the negative numbers are all in pairs
            MAX = max(MAX, product(nums, 0, len - 1));
        } else {
            // before last negtive or after first negtive
            int leftMul = product(nums, 0, endNeg - 1);
            int leftOverlap = product(nums, 0, startNeg);
            int rightOverlap = product(nums, endNeg, len - 1);
            int rightMul = leftMul * rightOverlap / leftOverlap;
            MAX = max(MAX, leftMul);
            MAX = max(MAX, rightMul);
        }
        if (len != nums.size()) {
            MAX = max(MAX, 0);
        }
        return MAX;
    }
    
    int product(vector<int>& nums, int low, int high) {
        int mul = 1;
        for (int i = low; i <= high; i++) {
            mul *= nums[i];
        }
        return mul;
    }
};

方法二

算法思路

方法一思路虽然清晰,但代码并不优雅,还是有很多细节要填坑,于是就有了下面的解法

前面说到,限制是遍历时并不知道后续是否存在负数,那我就把 正数负数 都存起来不就行了?

动态规划 思路如下

  • 需要记录的是正数最大值和负数最小值,两个极值

  • 下一节点的极值,要么是顺着 上一状态的极值,要么就是 该节点

AC 代码

class Solution {
public int maxProduct(int[] A) {
    if (A.length == 0) {
        return 0;
    }
    
    int maxherepre = A[0];
    int minherepre = A[0];
    int maxsofar = A[0];
    int maxhere, minhere;
    
    for (int i = 1; i < A.length; i++) {
        maxhere = Math.max(Math.max(maxherepre * A[i], minherepre * A[i]), A[i]);
        minhere = Math.min(Math.min(maxherepre * A[i], minherepre * A[i]), A[i]);
        maxsofar = Math.max(maxhere, maxsofar);
        maxherepre = maxhere;
        minherepre = minhere;
    }
    return maxsofar;
}

linckye
32 声望1 粉丝

成功捕获一枚 Java


« 上一篇
排列组合