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