0. 棧思想
利用棧的特性(先進(jìn)后出)來(lái)解決問(wèn)題,適合的題目類(lèi)型:
- 數(shù)據(jù)是線性的
- 問(wèn)題中常常設(shè)計(jì)到數(shù)據(jù)的來(lái)回比較赖晶、匹配問(wèn)題,如括號(hào)匹配、每日溫度遏插、字符串解碼捂贿、去掉重復(fù)字母等問(wèn)題。
- 問(wèn)題中涉及到數(shù)據(jù)的轉(zhuǎn)置胳嘲,如進(jìn)制問(wèn)題厂僧、鏈表倒序打印等。
1. 括號(hào)匹配檢驗(yàn)
假設(shè)表達(dá)式中允許包含兩種括號(hào):圓括號(hào)和方括號(hào),其嵌套的順序隨意,即([]())?
或?[([][])]
等為正確的格式了牛,[(]
或([())
或(()])
均為不正確的格式颜屠。輸入一個(gè)包含上述括號(hào)的表達(dá)式,檢驗(yàn)括號(hào)是否配對(duì)鹰祸。
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef char SElemType;
/* 鏈棧結(jié)構(gòu) */
typedef struct StackNode {
SElemType data;
struct StackNode *next;
} StackNode, *StackNodePtr;
typedef struct {
StackNodePtr top;
} LinkStack;
Status Push(LinkStack *S, SElemType e) {
if (!S) return ERROR;
StackNodePtr node = (StackNodePtr)malloc(sizeof(StackNode));
node->data = e;
node->next = S->top; // 頭插法
S->top = node; // 更換頭結(jié)點(diǎn)
return OK;
}
Status Pop(LinkStack *S, SElemType *e) {
if (!S || !S->top) return ERROR;
*e = S->top->data;
StackNodePtr node = S->top;
S->top = S->top->next; // 更換頭結(jié)點(diǎn)
free(node);
return OK;
}
Status BracketsCheck(char *string) {
LinkStack stack;
stack.top = NULL;
char *p = string;
while (*p) {
if (*p == '[' || *p == '(') {
if (!Push(&stack, *p)) {
return FALSE;
}
} else if (*p == ']') {
char bracket = 0;
if (!Pop(&stack, &bracket) || bracket != '[') {
return FALSE;
}
} else if (*p == ')') {
char bracket = 0;
if (!Pop(&stack, &bracket) || bracket != '(') {
return FALSE;
}
}
p++;
}
return TRUE;
}
int main(int argc, const char * argv[]) {
if (BracketsCheck("[]()")) {
printf("括號(hào)格式正確\n");
} else {
printf("括號(hào)格式錯(cuò)誤\n");
}
return 0;
}
2. 每日氣溫
根據(jù)每日 氣溫 列表甫窟,請(qǐng)重新生成一個(gè)列表,對(duì)應(yīng)位置的輸入是你需要再等待多久溫度才會(huì)升高的天數(shù)蛙婴。如果之后都不會(huì)升高粗井,請(qǐng)輸入 0 來(lái)代替。
例如街图,給定一個(gè)列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73]浇衬,你的輸出應(yīng)該是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:氣溫 列表長(zhǎng)度的范圍是 [1, 30000]餐济。每個(gè)氣溫的值的都是 [30, 100] 范圍內(nèi)的整數(shù)耘擂。
思路1
- 棧里面的值,是從大到小排列的絮姆,棧底溫度最高醉冤,棧頂溫度最低
- 如果棧里面有值,當(dāng)前溫度比比棧頂溫度更高滚朵,依次彈出溫度更低的節(jié)點(diǎn)冤灾,并和當(dāng)前索引進(jìn)行計(jì)算獲取像個(gè)天數(shù)
- 新的溫度入棧,原棧頂?shù)囊幌盗袦囟雀偷墓?jié)點(diǎn)都被彈出了辕近,現(xiàn)在新的溫度就是最低的溫度
typedef struct Temp {
int index;
int temp;
} Temp;
int* dailyTemperatures(int* T, int TSize, int* returnSize) {
int *ans = (int *)calloc(TSize, sizeof(int ));
Temp *stack = (Temp *)malloc(sizeof(Temp) * TSize);
*returnSize = TSize;
int top = -1;
for (int i = 0; i < TSize; i++) {
if (top > -1 && T[i] > stack[top].temp) { // 棧中有值韵吨,且當(dāng)前溫度比棧頂溫度更高
for (; top > -1 && T[i] > stack[top].temp; top--) { // 遍歷比棧頂溫度低的溫度
ans[stack[top].index] = i - stack[top].index; // 計(jì)算相隔天數(shù)
}
}
// 入棧當(dāng)前溫度
top++;
stack[top].index = i;
stack[top].temp = T[i];
}
return ans;
}
int main(int argc, const char * argv[]) {
int temperatures[8] = {73, 74, 75, 71, 69, 72, 76, 73};
int returnSize;
int *result = dailyTemperatures(temperatures, 8, &returnSize);
for (int i = 0; i < returnSize; i++) {
printf("%d ", result[i]);
}
printf("\n");
return 0;
}
思路2
- 從后往前進(jìn)行計(jì)算,緩存第 天再過(guò) 天溫度更高
- 當(dāng)前溫度還是從前往后遍歷
- 比如第 天比第 天溫度更高移宅,那么我們直接跳過(guò) 天就可以拿到更高的溫度归粉,而不需要依次比較
- 如果第 天后沒(méi)有哪一天溫度更高,則結(jié)束查找
int *dailyTemperatures_2(int* T, int TSize, int* returnSize){
int *result = (int *)malloc(sizeof(int) * TSize);
*returnSize = TSize;
result[TSize-1] = 0; // 最后一個(gè)后面沒(méi)有了
// 倒著計(jì)算溫度間隔漏峰,倒數(shù)第一個(gè)已經(jīng)有了糠悼,從倒數(shù)第二個(gè)開(kāi)始
for (int i = TSize-2; i >= 0; i--) {
// 從當(dāng)前位置后一個(gè)位置開(kāi)始向后遍歷,T[i]為當(dāng)前溫度浅乔,T[j]為索引溫度
// 通過(guò)result[j]可以知道需要前進(jìn)的天數(shù)倔喂,來(lái)獲取更高的溫度
for (int j = i+1; j < TSize; j+=result[j]) {
if (T[i] < T[j]) { // 當(dāng)前溫度比索引溫度低铝条,則找到了
result[i] = j-i;
break;
} else if (result[j] == 0) { // 當(dāng)前溫度比索引溫度高,但是后面沒(méi)有比這個(gè)溫度更高的了
result[i] = 0;
break;
}
}
}
return result;
}
3. 爬樓梯問(wèn)題
假設(shè)你正在爬樓梯席噩。需要 n 階你才能到達(dá)樓頂班缰。
每次你可以爬 1 或 2 個(gè)臺(tái)階。你有多少種不同的方法可以爬到樓頂呢悼枢?
注意:給定 n 是一個(gè)正整數(shù)埠忘。
3.1 暴力遞歸
根據(jù)之前的步數(shù),嘗試走1步或者2步馒索,看會(huì)不會(huì)到達(dá)階梯總數(shù)莹妒。
結(jié)束條件:
- 階梯總數(shù)比走過(guò)的步數(shù)小 ,則這不是一種可行的方法绰上,返回0旨怠。
- 階梯總數(shù)等于走過(guò)的步數(shù) ,則這是一種方法渔期,返回1运吓。
通過(guò)畫(huà)圖,我們可以發(fā)現(xiàn)這是一個(gè)樹(shù)形結(jié)構(gòu)疯趟。
時(shí)間復(fù)雜度:
空間復(fù)雜度:
static int climb_stairs(int step, int n) {
if (step > n) {
return 0;
} else if (step == n) {
return 1;
}
return climb_stairs(step + 1, n) + climb_stairs(step + 2, n);
}
int climbStairs(int n) {
return climb_stairs(0, n);
}
3.2 動(dòng)態(tài)規(guī)劃
第 階可以由以下兩種方法得到:
- 在第階后向上爬1階。
- 在第階后向上爬 2 階谋梭。
令 表示能到達(dá)第 階的方法總數(shù):
這里我們使用一個(gè)數(shù)組來(lái)進(jìn)行結(jié)果的統(tǒng)計(jì)信峻。
時(shí)間復(fù)雜度:
空間復(fù)雜度:
int climbStairs (int n) {
if (n == 1) return 1;
if (n == 2) return 2;
int *a = (int *)malloc(sizeof(int) * (n + 1));
a[1] = 1;
a[2] = 2;
for (int i = 3; i <= n; i++) {
a[i] = a[i - 1] + a[i - 2];
}
n = a[n];
free(a);
return n;
}
3.3 進(jìn)一步優(yōu)化
我們發(fā)現(xiàn),這就是一個(gè)斐波那契數(shù)列瓮床,且下一個(gè)參數(shù)只和前兩個(gè)項(xiàng)有關(guān)盹舞,那么這兩項(xiàng)之前的空間是沒(méi)有必要的。
我們只是有2個(gè)參數(shù)來(lái)保留前兩項(xiàng)的結(jié)果就行了隘庄。
時(shí)間復(fù)雜度:
空間復(fù)雜度:
int climbStairs (int n) {
if (n == 1) return 1;
int first = 1, second = 2;
for (int i = 3, third = 0; i <= n; i++) {
third = first + second;
first = second;
second = third;
}
return second;
}
4. 去除重復(fù)字符
給你一個(gè)僅包含小寫(xiě)字母的字符串踢步,請(qǐng)你去除字符串中重復(fù)的字母,使得每個(gè)字母只出現(xiàn)一次丑掺。需保證返回結(jié)果的字典序最谢裼 (要求不能打亂其他字符的相對(duì)位置)。
示例 1:
輸入: "bcabc"
輸出: "abc"
示例 2:
輸入: "cbacdcbc"
輸出: "acdb"
思路:
- 字符a-z一共26個(gè)街州,加上結(jié)束符
\0
有27個(gè)兼丰,所以我們申請(qǐng)一個(gè)大小為27的char
類(lèi)型的棧。 - 先遍歷一遍唆缴,看每個(gè)字符出現(xiàn)的次數(shù)鳍征。
- 后期遍歷的時(shí)候可以知道,是否還可以刪除面徽。(使得每個(gè)字母只出現(xiàn)一次)
- 再加入一個(gè)標(biāo)記艳丛,數(shù)組里面是否已經(jīng)存在某個(gè)字符了。
- 存在、后面還有氮双,就可以刪除碰酝,后面會(huì)添加回來(lái)。
- 要求字典序眶蕉,那么數(shù)組中的字符基本是由小到大排列的砰粹。(不是一定是,因?yàn)檫€要滿足每個(gè)字母只出現(xiàn)一次)
核心的處理:
- 當(dāng)前字符沒(méi)有入棧
- 棧不是空棧造挽、當(dāng)前字符比棧頂字符小碱璃、棧頂?shù)脑睾竺孢€有時(shí),我們可以把滿足這些條件的棧頂元素彈出饭入。(后面還有機(jī)會(huì)再加回來(lái))
- 再入棧當(dāng)前字符嵌器。(此時(shí)如果前面沒(méi)有再也不出現(xiàn)的字符,這個(gè)就是最小的字符)
- 結(jié)束遍歷時(shí)谐丢,棧頂加上結(jié)束符
\0
爽航,就是我們的目標(biāo)字符串了。
char * removeDuplicateLetters(char *s) {
if (!s || strlen(s) < 2) return s;
int top = -1; // 棧頂
char *stack = malloc(sizeof(char) * 27); // 給后面配置\0多加1
char inStack[26] = {0}; // 是否入棧
int charCount[26] = {0}; // 字符剩余總數(shù)
// 統(tǒng)計(jì)單個(gè)字符總數(shù)
char *p = s;
while (*p) {
charCount[*p - 'a']++;
p++;
}
p = s;
int idx = 0; //字符索引
while (*p) {
idx = *p - 'a'; // 當(dāng)前字符在數(shù)組中的索引值
charCount[idx]--; // 更新元素的剩余次數(shù)
if (!inStack[idx]) { // 當(dāng)前字符沒(méi)有入棧
// 是否需要出棧棧頂字符
while (top != -1 // 空棧不處理
&& *p < stack[top] // 當(dāng)前字符比棧頂字符小
&& charCount[stack[top] - 'a'] > 0) { // 棧頂字符在后面還有乾忱,就可以出棧
inStack[stack[top] - 'a'] = 0; // 棧頂彈出讥珍,就沒(méi)有入棧了
--top; // 出棧
}
// 當(dāng)前字符沒(méi)有入棧,就可以入棧
stack[++top] = *p;
inStack[idx] = 1;
}
p++; // 繼續(xù)遍歷
}
stack[++top] = '\0'; // 循環(huán)已保證棧底到棧頂是排好序的了窄瘟,加上結(jié)束符
return stack;
}
5. 字符串解碼
給定一個(gè)經(jīng)過(guò)編碼的字符串衷佃,返回它解碼后的字符串。
編碼規(guī)則為:k[encoded_string]
蹄葱,表示其中方括號(hào)內(nèi)部的encoded_string
正好重復(fù)k
次氏义,k
為正整數(shù)。
你可以認(rèn)為輸入字符串總是有效的图云;輸入字符串中沒(méi)有額外的空格惯悠,且輸入的方括號(hào)總是符合格式要求的。
此外竣况,你可以認(rèn)為原始數(shù)據(jù)不包含數(shù)字克婶,所有的數(shù)字只表示重復(fù)的次數(shù) k ,例如不會(huì)出現(xiàn)像3a
或2[4]
的輸入帕翻。
示例:
s = "3[a]2[bc]", 返回 "aaabcbc".
s = "3[a2[c]]", 返回 "accaccacc".
s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".
#define MAXSIZE 2000
typedef char* StrType;
typedef struct StrStackNode
{
int multi;
StrType data;
struct StrStackNode *next;
} StrStackNode, *StrStackNodePtr;
typedef struct
{
StrStackNodePtr top;
int count;
} StrLinkStack;
void PushStr(StrLinkStack *S, int multi, StrType e) {
StrStackNodePtr node = (StrStackNodePtr)malloc(sizeof(StrStackNode));
node->multi = multi;
node->data = e;
node->next = S->top;
S->top = node;
}
void PopStr(StrLinkStack *S, int *multi, StrType *e) {
StrStackNodePtr node = S->top;
*multi = node->multi;
*e = node->data;
S->top = S->top->next;
free(node);
}
char * decodeString(char *s)
{
int multi = 0;
StrType res = (StrType)malloc(sizeof(char) * MAXSIZE); // 記錄當(dāng)前的字符串
*res = '\0';
StrLinkStack strStack = {NULL, 0};
StrType p = s;
while (*p) {
if(*p >= '0' && *p <= '9') { // 計(jì)算出現(xiàn)次數(shù)
multi = multi * 10 + *p - '0';
} else if (*p == '[') { // 入棧'['之前的次數(shù)和字符串
PushStr(&strStack, multi, res);
multi = 0;
res = (StrType)malloc(sizeof(char) * MAXSIZE);
*res = '\0';
} else if (*p == ']') { // 出棧'['之前的次數(shù)和字符串
StrType curStr = res; // 當(dāng)前是嵌套最深的字符串鸠补,如3[a2[cd]]中的cd
int curMulti = 0;
PopStr(&strStack, &curMulti, &res); // 出棧,如3[a2[cd]]中的2和"a"
StrType tmp = (StrType)malloc(sizeof(char) * MAXSIZE);
*tmp = '\0';
for (int i = 0; i < curMulti; i++) { // 拼接嘀掸,如3[a2[cd]]中的2[cd]
strcat(tmp, curStr);
}
strcat(res, tmp); // 拼接紫岩,如3[a2[cd]]中的"a"和2[cd]生成的cdcd
} else {
strncat(res, p, 1); // 在字符串中追加一個(gè)字符
}
p++;
}
return res;
}
6. 楊輝三角
在楊輝三角中,每個(gè)數(shù)是它左上方和右上方的數(shù)的和睬塌。
示例:
輸入: 5
輸出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
思路:
如果要求第n行的顯示可能還會(huì)考慮遞歸泉蝌。這里要求每行都要返回歇万,直接就按題目意思,使用動(dòng)態(tài)規(guī)劃就好了勋陪。
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int** generate(int numRows, int* returnSize, int** returnColumnSizes){
int size, i;
int **res = (int **)malloc(sizeof(int *) * numRows);
int *columnSizes = (int *)malloc(sizeof(int) * numRows);
for (int j = 0; j < numRows; j++) {
size = j + 1;
columnSizes[j] = size;
res[j] = (int *)malloc(sizeof(int) * size);
res[j][0] = 1;
res[j][size-1] = 1;
if (j > 1)
for (i = 1; i < size-1; i++)
res[j][i] = res[j-1][i-1] + res[j-1][i];
}
*returnColumnSizes = columnSizes;
*returnSize = numRows;
return res;
}
int main(int argc, const char * argv[]) {
int **res, *returnColumnSizes;
int returnSize;
int numRows = 5;
res = generate(numRows, &returnSize, &returnColumnSizes);
for (int i = 0; i < returnSize; i++) {
for (int j = 0; j < returnColumnSizes[i]; j++) {
printf("%d ", res[i][j]);
}
printf("\n");
}
free(res);
free(returnColumnSizes);
return 0;
}
// 輸出
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
7. 七進(jìn)制數(shù)
給定一個(gè)整數(shù)贪磺,將其轉(zhuǎn)化為7進(jìn)制,并以字符串形式輸出诅愚。
示例 1:
輸入: 100
輸出: "202"
示例 2:
輸入: -7
輸出: "-10"
注意: 輸入范圍是 [-1e7, 1e7] 寒锚。
思路:
-
范圍 [-1e7, 1e7] 。
int main(int argc, const char * argv[]) { int i = 1; int num = 1e7; while (num) { num = num / 7; i++; } printf("%d\n", i); return 0; } // 輸出 10
考慮到還有負(fù)號(hào)
-
违孝,所以數(shù)組的長(zhǎng)度至少為11刹前。 -
進(jìn)制轉(zhuǎn)換
我們可以看到通過(guò)不斷取余
%
和求商/
操作,就可以拿到的系數(shù)雌桑,再加上'0'
就是我們的字符表示了喇喉。 二進(jìn)制字符表示是從高位到低位的,但我們上面的操作是從低位到高位進(jìn)行獲取的校坑,所以我們把順序數(shù)組改成棧的表示拣技。最后出棧顯示最終結(jié)果。
char * convertToBase7(int num) {
if (num == 0) return "0";
int top = -1;
char *stack = (char *)malloc(sizeof(char) * 11);
// 確定正負(fù)
int flag = 0;
if (num < 0) {
flag = 1;
num = -num;
}
// 進(jìn)制轉(zhuǎn)換耍目,入棧
char rest;
while (num > 0) {
rest = num % 7 + '0';
num = num / 7;
stack[++top] = rest;
}
if (flag) stack[++top] = '-';
// 出棧輸出結(jié)果
int count = top + 1;
char *res = (char *)malloc(sizeof(char) * (count + 1));
int i = 0;
for (; i < count; i++) {
res[i] = stack[top--];
}
res[i] = '\0';
free(stack);
return res;
}
8. 刪除字符串中的所有相鄰重復(fù)項(xiàng)
個(gè)人發(fā)現(xiàn)的一道比較有意思的題膏斤,有點(diǎn)像祖瑪游戲,中間消除之后邪驮,如果兩個(gè)相同元素相遇仍然可以消除掸绞。
給出由小寫(xiě)字母組成的字符串 S,重復(fù)項(xiàng)刪除操作會(huì)選擇兩個(gè)相鄰且相同的字母耕捞,并刪除它們。
在 S 上反復(fù)執(zhí)行重復(fù)項(xiàng)刪除操作烫幕,直到無(wú)法繼續(xù)刪除俺抽。
在完成所有重復(fù)項(xiàng)刪除操作后返回最終的字符串。答案保證唯一较曼。
示例:
輸入:"abbaca"
輸出:"ca"
解釋?zhuān)?br> 例如磷斧,在 "abbaca" 中,我們可以刪除 "bb" 由于兩字母相鄰且相同捷犹,這是此時(shí)唯一可以執(zhí)行刪除操作的重復(fù)項(xiàng)弛饭。之后我們得到字符串 "aaca",其中又只有 "aa" 可以執(zhí)行重復(fù)項(xiàng)刪除操作萍歉,所以最后的字符串為 "ca"侣颂。
提示:
1 <= S.length <= 20000
S 僅由小寫(xiě)英文字母組成。
思路:
這里為了不適用額外的存儲(chǔ)空間枪孩,使用了雙指針?biāo)枷牒蜅K枷搿?/p>
快指針進(jìn)行遍歷憔晒,慢指針表示棧頂藻肄,最后添加結(jié)束符。
char * removeDuplicates(char * S){
int top = 0;
for (int i = 0; S[i]; i++, top++) {
S[top] = S[i];
if (top > 0 && S[top] == S[top - 1])
top -= 2;
}
S[top] = '\0';
return S;
}