【算法题】LeetCode Array(1)

前言

好几天前开始就想刷刷LeetCode,然而一波三折。出于某种强迫症,还是想继续用VS Code写,然而VS Code + MinGW怎么也配置不好。debug、代码补全、编译路径、Code Runner······总是出各种问题,折腾了一天多现在勉强能用了,有时间写一下博客总结下教训。毕竟不是写工程,Code Blocks、VS啥的写一个题切一个也怪麻烦的。
环境配置好了想了想还是按照标签刷,那就从最简单的Array开始吧!然而发现题目也没有我想象的那么简单,提交成功率可以说极低,代码一点也谈不上优雅,有些没办法还采用了暴力算法。加上很久没用C++了,所以很多东西都忘了,就这样跌跌撞撞地也没做完几个题。不过这些题还是挺有意思的,我先放上我丑陋的代码,有的甚至通不过测试用例,然后从论坛找个好点的分析一下吧,研究下我的代码为何如此垃圾。

11. Container With Most Water

原题

Given n non-negative integers a1, a2, …, an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
Note:
You may not slant the container and n is at least 2.

The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
Example:
Input: [1,8,6,2,5,4,8,3,7]
Output: 49

我的代码

class Solution {
public:
    int maxArea(vector<int>& height) {
        int size = height.size();
        int maxArea = 0;
        for (int i = 0; i < size; i++) {
            for (int j = i + 1; j < size; j++) {
                int h = height[i] < height[j] ? height[i] : height[j];
                int area = (j - i) * h;
                if (area > maxArea)
                    maxArea = area;
            }
        }
        return maxArea;
    }
};

论坛代码

    int maxArea(vector<int>& height) { // Two pointers
        int maxArea = 0, leftIndex = 0, distance = 0;
        int rightIndex = (int)(height.size() - 1);
        while (leftIndex < rightIndex) { // We can't have a window that isn't valid
            distance = rightIndex - leftIndex; // Base of rectangle
            maxArea = max(min(height[leftIndex], height[rightIndex]) * distance, maxArea); 
            if (height[leftIndex] < height[rightIndex]) { // Move the smaller leg in hopes
                leftIndex++; // of finding a larger leg to maximize area
            } else {
                rightIndex--;
            }
        }
        return maxArea;
    }

问题分析

这个题题意还是比较好懂,找一个最大矩形,这就需要在底和高之间权衡。看得出,我拙劣的代码暴力求了,还好测试用例里没有特别极端的,最终还是通过了。但还是看看论坛里不错的代码。
论坛代码的思路首先是选取最大的底,然后缩小以权衡放大高。缩小的过程比较有讲究,左右的高都可以移动。如果左边的高小于右边的,可以左边的高向右移动一位,否则右边的高向左移动一位。这个过程中每次得到新的矩形都要与最大的比较,如果超过则取而代之。这样遍历难免会让人怀疑会错过一些,但是错过的其实都一定都比当前小。想象一下,如果左边高于右边,那么还向右移动,底缩小,但是高一定不会增大,因为高是以左右最小的为准,这种情况下右边是瓶颈,所以移动后必定小于当前的,但如果右边移动则有可能得到更大的面积,另一个方向的证明同理。

15. 3Sum

原题

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
The solution set must not contain duplicate triplets.
Example:
Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]

我的代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        set<vector<int>> vecsSet;
        sort(nums.begin(), nums.end());
        int size = nums.size();
        int first = 0;
        int second = -1;
        for (int i = 0; i < size; i++) {
            for (int j = i + 1; j < size - 1; j++) {
                if (nums[i] == first && nums[j] == second)
                    continue;
                int toFind = 0 - nums[i] - nums[j];
                int begin = j + 1;
                int end = size - 1;
                int mid = begin;
                while (begin <= end) {
                    mid = (begin + end) / 2;
                    if (nums[mid] < toFind)
                        begin = mid + 1;
                    else if (nums[mid] > toFind)
                        end = mid - 1;
                    else
                        break;
                }
                if (nums[mid] == toFind) {
                    vector<int> vec = {nums[i], nums[j], toFind};
                    sort(vec.begin(), vec.end());
                    vecsSet.insert(vec);
                }
            }
        }
        vector<vector<int>> result(vecsSet.begin(), vecsSet.end());
        return result;
    }
};

论坛代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if (nums.size() < 3) return {};
        sort(nums.begin(), nums.end());      
        
        vector<vector<int>> result;
        int j, k, sum;
        for (int i = 0; i < nums.size()-1 && nums[i] <= 0; i++){
            if (i > 0 && nums[i] == nums[i-1]) continue;
            j = i+1;
            k = nums.size()-1;
            sum = -1;
            while (j < k){
                sum = nums[i] + nums[j] + nums[k];
                if (sum > 0) --k;
                else if (sum < 0) ++j;
                else if (j < k-1 && nums[k] == nums[k-1]) --k;
                else if (j+1 < k && nums[j] == nums[j+1]) ++j;
                else {
                    result.push_back({nums[i],nums[j],nums[k]});
                    --k;
                    ++j;
                }
            }
       }
        return result;
    }
};

问题分析

这个题我足足提交了十几遍也没通过。大部分是超时,个别是重复项没有控制好。不得不说测试用例设置的真好,很多可能性都想到了,我的代码被多次refuse。

Accepted 766,292 Submissions 2,989,859

这个通过率还是能说明一些问题的,坑确实很多。上个题的暴力求法在这个题一点用也没有了,总之还是先看了看提示。

提示的思路大概是先定住第一个数,然后就变成的two-sum问题。对于两个数再定住一个数,然后因为和为0,最后一个数也就固定了,去找就可以。三层循环O(n3)确实复杂度爆炸,提示里说的已经很清楚了可以预处理一下数值,我就想到了先排序,然后二分查找最后一个数。至于重复项用set控制一下,最后再转成题目需要的vector。不过经过几番测试这个复杂度还是太高,测试用例有一个是6000个0(确实服了),所以还需要更加巧妙的方法。没办法,我最后还是看了论坛。另外提示3的意思好像是空间换时间?我还不是太懂。
我们来尝试理解下论坛里这个方法。
首先也是对数组进行排序,然后进入第一层循环,如果某个数字和前一个相同,那么就跳过,显然这是为了避免重复,比我用set聪明多了。然后k设置为最后一个数的index,进入第二层循环。可以看到,循环是以i后面第一个数(j)和最后数为左右边界互相逼近。首先计算num[i]和两个边界处数值的和,如果大于0说明第j个或者第k个数大了,然而j已经不能再小了,只能k减小。如果小于0说明小了,而k已经不能增大了,则增大j。如果正好等于0,又分为几种情况。如果j+1还是小于k,也就是左右边界之间还有数字,并且右边界和右边界左边第一个即k-1相等,那么k = k - 1,防止重复;如果左边界和坐标第一个即j+1相等,那么j = j + 1,同样是防止重复,最后拉到最近以后便可以push进来,并且左右边界继续移动。另外注意一点第一层循环要保证nums[i] <= 0,显然,如果三者都大于零和不可能为0。这里我想到,如果和是任意一个数n,那么所有的数字都减去n,还是可以用这个方法。
总的来说这个思路还是很巧的,通过合理的算法将复杂度缩小到n2。左右边界的控制既防止了重复又减少了不必要的遍历,这个思路需要好好去体会。

950. Reveal Cards In Increasing Order

原题

In a deck of cards, every card has a unique integer. You can order the deck in any order you want.

Initially, all the cards start face down (unrevealed) in one deck.

Now, you do the following steps repeatedly, until all cards are revealed:

  • Take the top card of the deck, reveal it, and take it out of the deck.
  • If there are still cards in the deck, put the next top card of the deck at the bottom of the deck.
  • If there are still unrevealed cards, go back to step 1. Otherwise, stop.

Return an ordering of the deck that would reveal the cards in increasing order.

The first entry in the answer is considered to be the top of the deck.

Example 1:

Input: [17,13,11,2,3,5,7]
Output: [2,13,3,11,5,17,7]
Explanation:
We get the deck in the order [17,13,11,2,3,5,7] (this order doesn’t matter), and reorder it.
After reordering, the deck starts as [2,13,3,11,5,17,7], where 2 is the top of the deck.
We reveal 2, and move 13 to the bottom. The deck is now [3,11,5,17,7,13].
We reveal 3, and move 11 to the bottom. The deck is now [5,17,7,13,11].
We reveal 5, and move 17 to the bottom. The deck is now [7,13,11,17].
We reveal 7, and move 13 to the bottom. The deck is now [11,17,13].
We reveal 11, and move 17 to the bottom. The deck is now [13,17].
We reveal 13, and move 17 to the bottom. The deck is now [17].
We reveal 17.
Since all the cards revealed are in increasing order, the answer is correct.

Note:

  • 1 <= A.length <= 1000
  • 1 <= A[i] <= 10^6
  • A[i] != A[j] for all i != j

我的代码

class Solution {
public:
    vector<int> deckRevealedIncreasing(vector<int>& deck) {
        sort(deck.rbegin(), deck.rend());
        vector<vector<int> > vecs;
        vector<int> nums;
        int num = deck.size();
        do {
            int num_in = (num+1)/2;
            vector<int> vec;
            for (int i = 0; i < num_in; i++) {
                vec.push_back(deck.back());
                deck.pop_back();
            }
            vecs.push_back(vec);
            num = num - num_in;
        } while (num >= 1);
        int vec_num = vecs.size();
        for (int i = vec_num - 2; i >= 0; i--) {
            vector<int> *a = &vecs[i];
            vector<int> *b = &vecs[i+1];
            for (int j = 0; j < b->size(); j++) {
                a->insert(a->begin()+1+j*2, (*b)[j]);
            }
            if (i != 0 && a->size() < vecs[i-1].size()) {
                a->insert(a->begin(), a->back());
                a->pop_back();
            }
            printVector(*a);
        }
        return vecs.front();
    }
};

论坛代码

class Solution {
public:
	vector deckRevealedIncreasing(vector& deck) {
	    sort(deck.begin(),deck.end());
	    int n=deck.size();
	    vector<int>res(n,0);
	    res[0]=deck[0];
	    int p=0;
	    for(int i=1;i<deck.size();i++){
	        int j=0;
	        while(j<2){ 
	            p++;
	            p=p%n;
	            if(res[p]==0){
	                j++;
	            }
	        }
	        res[p]=deck[i];
	    }
	    return res;
	}
};

问题分析

我的方法还是一如既往的笨,思路就是找规律吧,先划分了一下数组,然后小的插入到大的数组里,适当要做一些末位前置。
看看论坛的方法,首先排序,然后进入第一层循环。循环中j从0开始,然后进入第二层循环,循环内部索引p环形遍历,数到第二个空位然后结果矩阵赋值为原矩阵排序后的第i个数(从1开始),直到遍历完。
代码还是比较好看懂的,但是为什么要这样做呢?感觉他的方法其实才是根据揭牌的原理,隔一个赋一个值,一遍以后从头再来寻找空位,直到赋值完成。

1329. Sort the Matrix Diagonally

原题

Given a m * n matrix mat of integers, sort it diagonally in ascending order from the top-left to the bottom-right then return the sorted array.

Example 1:
Input: mat = [[3,3,1,1],[2,2,1,2],[1,1,1,2]]
Output: [[1,1,1,1],[1,2,2,2],[1,2,3,3]]
Constraints:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 100
  • 1 <= mat[i][j] <= 100

我的代码

class Solution {
public:
    vector<vector<int> > diagonalSort(vector<vector<int> >& mat) {
        int m = mat.size();
        int n = mat[0].size();
        vector<vector<int> > output;
        // Initialize
        for (int i = 0; i < m; i++) {
            output.push_back(*(new vector<int>));
            for (int j = 0; j < n; j++) {
                output[i].push_back(0);
            }
        }
        for (int k = 0; k < m; k++) {
            vector<int> toSort;
            int j = 0;
            for (int i = k; j < n && i < m; i++) {
                toSort.push_back(mat[i][j]);
                j++;
            }
            sort(toSort.begin(), toSort.end());// use sort
            j = 0;
            for (int i = k; j < n && i < m; i++) { // control both i and j
                output[i][j] = toSort[j];
                j++;
            }
        }
        for (int k = 1; k < n; k++) {
            vector<int> toSort;
            int i = 0;
            for (int j = k; j < n && i < m; j++) {
                toSort.push_back(mat[i][j]);
                i++;
            }
            sort(toSort.begin(), toSort.end());
            i = 0;
            for (int j = k; j < n && i < m; j++) {
                output[i][j] = toSort[i];
                i++;
            }
        }
        return output;
    }
};

论坛代码

vector<vector<int>> diagonalSort(vector<vector<int>>& mat) {
    int m = mat.size(), n = mat[0].size();
    for (int p = 0; p < n + m; ++p) {
        vector<int> v;
        int pi = p < m ? p : 0, pj = max(0, p - m + 1);
        for (int i = pi, j = pj; i < m && j < n; ++i, ++j)
            v.push_back(mat[i][j]);
        sort(begin(v), end(v));
        for (int i = pi, j = pj, vp = 0; i < m && j < n; ++i, ++j, ++vp)
            mat[i][j] = v[vp];        
    }
    return mat;
}

问题分析

这个题我依然觉得自己的方法好笨,就是对角线分别排序然后再赋值回去,这个过程因为边界没控制好还出错了。
论坛上的方法首先第一层循环上界是m+n,利用了两边之和大于第三边,所有的对角线都不会超过它。然后如果p小于m也就是行数那么p不变否则等于0,而pj取0和p - m + 1的最大值。很简洁的写法,这样就分开了中心对角线上下。然后遍历对角线并排序,最后再赋值回去,直到所有对角线都遍历完成。思路其实和我类似,只不过他的方法更优雅更统一,这也正是我要学习的地方。

后记

总之,花了不少时间但没做几个题,确实之前有点眼高手低了,即是C++有点忘了,又是因为很多巧妙的思路没怎么见过而用了很笨的方法。相信熟能生巧吧,继续刷逐渐就好起来了。至于这几个题,感觉一些共性就是遍历的方式,比如环形遍历、比如两边向中间遍历等等,一定先要分析好问题想想有没有好的方法,不然即使暴力出来了Runtime也会out。

发布了75 篇原创文章 · 获赞 28 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Swocky/article/details/104228485
今日推荐