leetcode刷題筆記(3)

目錄

stack專題:

32    Longest Valid Parentheses

技巧題:

31. Next Permutation 

桶排序:

41    First Missing Positive

貪心算法:

 42.Trapping Rain Water

45    Jump Game II

回溯法專題:

40    Combination Sum II

神奇數(京東2018校招C/C++工程師筆試大題第二道)

494. Target Sum

 

 


 

stack專題:

32    Longest Valid Parentheses

給定一個只包含 '(' 和 ')' 的字符串,找出最長的包含有效括號的子串的長度。

示例 1:

輸入: "(()"
輸出: 2
解釋: 最長有效括號子串爲 "()"

示例 2:

輸入: ")()())"
輸出: 4
解釋: 最長有效括號子串爲 "()()"

注意這題棧的使用技巧:

1.stack裏面永遠只有(,而沒有 )。所以paranStack.pop()彈出的一定是(。

2.保存進stack裏的並不是(,而是( 的下標,所以paranStack.top()的值是( 的下標。

 

基本思路:

遍歷一遍數組

如果遇到左括號,把當前左括號的下標裝進stack裏面。

如果遇到右括號,則判斷當前棧是否爲空,如果爲空,說明沒有可以匹配的左括號,則跳過這個右括號,如果棧不爲空,說明棧中還有左括號可以匹配,那麼彈出一個左括號。彈出後如果棧爲空。說明,從lastValidIndx開始就一直都是合法的括號匹配。那麼maxLength=max(maxLength, indx-lastValidIndx+1);如果棧不爲空,說明當前只是匹配成功了一部分,並不是從一開始就一直匹配成功,在中間有個左括號沒有匹配到,那麼對於此時的情況來說,maxLength等於這個被卡住的左括號之後的匹配成功的括號數。比如輸入()((),此時lastValidIndx = 0,paranStack.top()爲2.

class Solution {
public:
    int longestValidParentheses(string s) {
     stack<int> paranStack;
        int maxLength=0;
        int lastValidIndx=0;
        for (int indx=0; indx<s.length(); indx++) {
            if (s[indx]=='(') //遇到左括號,直接存入。
                paranStack.push(indx);
            else { //遇到右括號,分情況討論
                if (paranStack.empty()) //如果此時棧裏左括號已經被消耗完了,沒有額外的左括號用來配對當前的右括號了,那麼當前的右括號就被單出來了,表明當前子串可以結束了,此時的右括號也成爲了下一個group的分界點,此時右括號下標爲index,所以下一個group的起始點爲index+1,相當於skip掉當前的右括號。
                    lastValidIndx=indx+1;
                else { //如果此時棧不空,可能有兩種情況,1)棧正好剩下1個左括號和當前右括號配對 2)棧剩下不止1個左括號,
                    paranStack.pop();
                    if (paranStack.empty())  //棧pop()之前正好剩下1個左括號,pop()之後,棧空了,此時group長度爲indx-lastValidIndx
                        maxLength=max(maxLength, indx-lastValidIndx+1);
                    else  //棧有pop()之前剩下不止1個左括號,此時額外多出的左括號使得新的group形成。如()(()())中index=4時,stack中有2個左括號
                        maxLength=max(maxLength, indx-paranStack.top());
                }
            }
        }
        return maxLength;
    }
};

技巧題:

31. Next Permutation 

實現獲取下一個排列的函數,算法需要將給定數字序列重新排列成字典序中下一個更大的排列。

如果不存在下一個更大的排列,則將數字重新排列成最小的排列(即升序排列)。

必須原地修改,只允許使用額外常數空間。

以下是一些例子,輸入位於左側列,其相應輸出位於右側列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

思路:從後向前掃描,碰到後一個數比前一個數更小,則記下這個數,並從這裏向後掃描(或者從末尾往前掃描也行),尋找剛好比這個數大的數,即讓頭變得更大。  然後反轉這個數之後的其他位置的數。讓尾巴更小。

Next Permutation

 

public class Solution {
    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        while (i >= 0 && nums[i + 1] <= nums[i]) {
            i--;
        }
        if (i >= 0) {
            int j = nums.length - 1;
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }
        reverse(nums, i + 1);
    }

    private void reverse(int[] nums, int start) {
        int i = start, j = nums.length - 1;
        while (i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

桶排序:

41    First Missing Positive

給定一個未排序的整數數組,找出其中沒有出現的最小的正整數。

示例 1:

輸入: [1,2,0]
輸出: 3

示例 2:

輸入: [3,4,-1,1]
輸出: 2

示例 3:

輸入: [7,8,9,11,12]
輸出: 1

 思路:先把數值小於數組長度的數安排在i+1的位置上。然後剩下超出數組長度的值不要管。直接重新遍歷一遍數組,找出沒有安排在合適位置上的數,返回最先找到的數的下標

比如

【3,4,5,6】

第一個for循環後,3被安排在了下標3的位置

【6,4,5,3】

重新掃描一遍數組,發現6 != 1,所以返回1

class Solution
{
public:
    int firstMissingPositive(int A[], int n)
    {
        for(int i = 0; i < n; ++ i)
            while(A[i] > 0 && A[i] <= n && A[A[i] - 1] != A[i])
                swap(A[i], A[A[i] - 1]);
        
        for(int i = 0; i < n; ++ i)
            if(A[i] != i + 1)
                return i + 1;
        
        return n + 1;
    }
};

 貪心算法:

 42.Trapping Rain Water

給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。

上面是由數組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 感謝 Marcos 貢獻此圖。

示例:

輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6

這題可以歸類爲貪心

【分析】

1. 從左往右掃描一遍,對於每個柱子,求取左邊最大值,保存進數組;

2. 從右往左掃描一遍,對於每個柱子,求最大右值,保存進數組;

3. 再掃描一遍,把每個柱子的面積並累加。

對於每個柱子,找到其左右兩邊最高的柱子,該柱子能容納的面積就是 min(leftMostHeight[i],rightMostHeight[i]) - A[i];
 

class Solution {
public:
    int trap(int A[], int n) {
        if(A == NULL || n < 1)return 0;
        int i;
 
		int* leftMostHeight = (int*)malloc(sizeof(int)*n);
		int* rightMostHeight = (int*)malloc(sizeof(int)*n);
 
		int maxHeight = 0;
		for(i = 0; i < n;i++){
			leftMostHeight[i] = maxHeight;
			if(maxHeight < A[i]){
                maxHeight = A[i];
            }
		}
 
		maxHeight = 0;
		for(i = n-1;i >= 0;i--){
			rightMostHeight[i] = maxHeight;
			if(maxHeight < A[i]){
                maxHeight = A[i];
            }
		}
 
		int water = 0;
		for(i =0; i < n; i++){
			int curWater = min(leftMostHeight[i],rightMostHeight[i]) - A[i];
			if(curWater > 0){
				water += curWater;
			}
		}
		return water;
    }
};

45    Jump Game II

這題思路就是貪心算法,貪心原則是 在上一個最遠距離的範圍內找到下一跳的最遠距離

比如【2,3,7,1,1,1,1】

第一跳在數字2的基礎上往後跳,到7的位置,那麼我現在要找下標是1-2這個範圍內的下一跳最大能跳多遠。

class Solution {  
public:  
    int jump(int A[], int n) {  
        int ret = 0;//當前跳數  
        int last = 0;//上一跳可達最遠距離  
        int cur = 0;//當前一跳可達最遠距  
        for (int i = 0; i < n; ++i) {  
            //無法向前繼跳直接返回  
            if(i>cur){  //有可能無論怎麼跳,都不能到達終點或者越過終點,比如[3,2,1,0,4]。
                return -1;  
            }  
            //需要進行下次跳躍,則更新last和當執行的跳數ret  
            if (i > last) {  
                last = cur;  
                ++ret;  
            }  
            //記錄當前可達的最遠點  
            cur = max(cur, i+A[i]);  
        }  
  
        return ret;  
    }  
};  

回溯法專題:

40    Combination Sum II

給定一個數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和爲 target 的組合。

candidates 中的每個數字在每個組合中只能使用一次。

說明:

  • 所有數字(包括目標數)都是正整數。
  • 解集不能包含重複的組合。 

示例 1:

輸入: candidates = 
[10,1,2,7,6,1,5]
, target = 
8
,
所求解集爲:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:

輸入: candidates = [2,5,2,1,2], target = 5,
所求解集爲:
[
  [1,2,2],
  [5]
] 

這題是用的標準的回溯法子集數框架寫的

比如:輸入[1,2,3,4]

那麼中間結果是:

1,2,3,4

1,3,4

1,4

2,3,4

2,4

3,4

4

列出了所有的組合情況,然後去篩選,滿足條件的就保存起來。

class Solution {
public:
    vector<vector<int> > combinationSum2(vector<int> &num, int target) {
        sort(num.begin(), num.end());
        vector<vector<int> > ret;
        vector<int> cur;
        Helper(ret, cur, num, target, 0);
        return ret;
    }
    void Helper(vector<vector<int> > &ret, vector<int> cur, vector<int> &num, int target, int position)
    {
        if(target == 0)
            ret.push_back(cur);
        else
        {
            for(int i = position; i < num.size() && num[i] <= target; i ++)
            {
                if(i != position && num[i] == num[i-1])
                    continue;
                cur.push_back(num[i]);
                Helper(ret, cur, num, target-num[i], i+1);
                cur.pop_back();
            }
        }
    }
};

 這題的解法可以用在京東18筆試題上:

神奇數(京東2018校招C/C++工程師筆試大題第二道)

廢話

上週末被學長遠程抓壯丁答狗東2018的C++筆試題。2個小時4道大題,一個人做確實時間緊,記錄一下學長甩給我第二題的神奇數。

想來自從6月份畢業就沒再做過題,手生寫的慢,好賴算是ac了,後來聽說這題現場ac率10%我也是挺吃鯨的 =、=

閒話不多說,看題。

神奇數(京東2018校招C/C++工程師筆試大題第二道)
時間限制:1秒
空間限制:32768K
題目描述:
東東在一本古籍上看到有一種神奇數,如果能夠將一個數的數字分成兩組,其中一組數字的和等於另一組數字的和,我們就將這個數成爲神奇數。例如242就是一個神奇數,我們能夠將這個數的數字分成兩組,分別是{2, 2}以及{4},而且這兩組數的和都是4。東東現在需要統計給定區間內中有多少個神奇數,即給定區間(l, r),統計這個區間中有多少個神奇數,請你來幫助他。

分析及解法
先把題目對神奇數的描述翻譯成人話:“給你一個數,按位拆開後分成兩堆兒,兩堆兒的和相等”。

我們把區間內的數字按位拆開可以得到一個n位的數組,然後求其組合:

CmnCnm (m = 1, 2, … n/2)

對組合結果求和,判斷是否等於按位總和的一半。
 

 這題用DP很難想出來,所以說不要在筆試的時候用DP,除非這題就是專門考你DP

#include <iostream>
#include <vector>
using namespace std;
int l=0,r=0;

//動態規劃
bool canPartition(vector<int>&dicts,int n,int sum)
{
    vector<vector<bool>> dp(n+1,vector<bool>(sum+1,false));
    for(int i=0; i<=n; i++)
        dp[i][0] = true;
    for(int j=1; j<=sum; j++){
        for(int i=1; i<=n; i++){
            dp[i][j] = dp[i-1][j]; //不選第i個元素
            if(j>=dicts[i-1])      //選第i個元素
                dp[i][j] = dp[i][j] || dp[i-1][j-dicts[i-1]];
        }
    }
    return dp[n][sum];
}

bool isSqs(int num)
{
    vector<int> dicts(10,0);
    int k=0,sum=0;
    while(num!=0){
        dicts[k++] = num%10;
        num /= 10;
        sum += dicts[k-1];
    }
    if(sum & 1)
        return false;
    return canPartition(dicts,k,sum>>1);
}

int sqs()
{
    if(r<11)
        return 0;
    if(r==11)
        return 1;
    int cnt = 1;
    for(int i=12; i<=r; i++){
        if(isSqs(i))
            cnt++;
    }
    return cnt;
}

int main()
{
    cin>>l>>r;
    cout<<sqs();
    return 0;
}

 這題回溯法跟上一題有所不同

    bool res1 = isfind( nums, sum, cur+nums[begin], begin+1 );
    bool res2 = isfind( nums, sum, cur, begin+1 );

這兩個遞歸式表達了一個數取還是不取兩種情況,聯想一下二叉樹的左右分支。

bool isfind(vector<int>& nums, int sum, int cur, int begin)
{
    if( begin == nums.size() ) return false;
    if( cur == sum / 2 ) return true;
    bool res1 = isfind( nums, sum, cur+nums[begin], begin+1 );
    if( res1 ) return true;
    bool res2 = isfind( nums, sum, cur, begin+1 );
    if( res2 ) return true;
    return res1 || res2;
}

bool fenjie(long long n)
{
  vector<int> dig;
  int sum = 0;
  if(n==0)
  {
    dig.push_back(0);
    return false;
  }
  int t =0;
   while(n>0)
   {
     t = n%10;
     n/=10;
dig.push_back(t);
sum+=t;
  }
  sort(dig.begin(),dig.end());
  if(sum&1)return false;
  return isfind(dig,sum,0,0);
}
void core()
{
  int n,m,ret =0;
  cin>>n>>m;
  for( int i = n; i <= m; i++ )
    {
       if (fenjie(i))ret++;
    }
  cout<<ret<<endl;
}
int main()
{ 
   core();  
}

當然也可以用回溯法框架做。

#include <bits/stdc++.h>
using namespace std;
int func(int *a,int l,int r,int num,int sum)
{
    int ans=0;
    for(int i=l; i<r; i++)
    {
        if((num+a[i])*2<=sum)
        {
            if((num+a[i])*2==sum)    return 1;
            else ans+=func(a,i+1,r,num+a[i],sum);
        }
    }
    return ans;
}
int main()
{
    int ans=0,l,r;
    scanf("%d%d",&l,&r);
    for(int i=l; i<=r; i++)
    {
 
        int sum=0,now=i,a[10],count=0;
        while(now)
        {
            sum+=now%10;
            a[count++]=now%10;
            now/=10;
        }
        if(sum&1)    continue;
        if(func(a,0,count,0,sum))    ans++;
    }
    printf("%d\n",ans);
    return 0;
}

 

來看一道相似解法的回溯問題:

494. Target Sum

                                  Root 
                                  / \
                               +1    -1
                               /\     /\
                             +2 -2  +2  -2
                             /\  /\ /\  /\
                           +3 -3 ...  +3 -3
class Solution {
public:
    int result;
    int findTargetSumWays(vector<int>& nums, int S) {
        rec(0, 0, nums, S);
        return result;
    }
    void rec(int sum, int count, vector<int>& nums, int S) {
        if(count == nums.size()) {
            if(sum == S)
                result++;
            return ;
        }
        rec(sum + nums[count], count + 1, nums, S);
        rec(sum - nums[count], count + 1, nums, S);
    }
 
};

帶備忘錄的版本:

public class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        if (nums == null || nums.length == 0){
            return 0;
        }
        return helper(nums, 0, 0, S, new HashMap<>());
    }
    private int helper(int[] nums, int index, int sum, int S, Map<String, Integer> map){
        String encodeString = index + "->" + sum;
        if (map.containsKey(encodeString)){
            return map.get(encodeString);
        }
        if (index == nums.length){
            if (sum == S){
                return 1;
            }else {
                return 0;
            }
        }
        int curNum = nums[index];
        int add = helper(nums, index + 1, sum - curNum, S, map);
        int minus = helper(nums, index + 1, sum + curNum, S, map);
        map.put(encodeString, add + minus);
        return add + minus;
    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章