目錄
1 擁有最多糖果的孩子
2020-6-1
給你一個數組 candies 和一個整數 extraCandies ,其中 candies[i] 代表第 i 個孩子擁有的糖果數目。對每一個孩子,檢查是否存在一種方案,將額外的 extraCandies 個糖果分配給孩子們之後,此孩子有 最多 的糖果。注意,允許有多個孩子同時擁有 最多 的糖果數目。
示例 1:
輸入:candies = [2,3,5,1,3], extraCandies = 3
輸出:[true,true,true,false,true]
解釋:
孩子 1 有 2 個糖果,如果他得到所有額外的糖果(3個),那麼他總共有 5 個糖果,他將成爲擁有最多糖果的孩子。
孩子 2 有 3 個糖果,如果他得到至少 2 個額外糖果,那麼他將成爲擁有最多糖果的孩子。
孩子 3 有 5 個糖果,他已經是擁有最多糖果的孩子。
孩子 4 有 1 個糖果,即使他得到所有額外的糖果,他也只有 4 個糖果,無法成爲擁有糖果最多的孩子。
孩子 5 有 3 個糖果,如果他得到至少 2 個額外糖果,那麼他將成爲擁有最多糖果的孩子。
示例 2:
輸入:candies = [4,2,1,1,2], extraCandies = 1
輸出:[true,false,false,false,false]
解釋:只有 1 個額外糖果,所以不管額外糖果給誰,只有孩子 1 可以成爲擁有糖果最多的孩子。
示例 3:
輸入:candies = [12,1,12], extraCandies = 10
輸出:[true,false,true]
提示:
2 <= candies.length <= 100
1 <= candies[i] <= 100
1 <= extraCandies <= 50
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
bool* kidsWithCandies(int* candies, int candiesSize, int extraCandies, int* returnSize){
bool* ans=(bool*)malloc(candiesSize*sizeof(bool));
*returnSize=0;
int max=0;
for(int i=0;i<candiesSize;i++){
if(candies[i]>max)max=candies[i];
}
for(int i=0;i<candiesSize;i++){
if(candies[i]+extraCandies>=max)ans[*returnSize]=true;
else ans[*returnSize]=false;
(*returnSize)++;
}
return ans;
}
兒童節快樂☺ ♪ ☑
2 求1+2+…+n
2020-6-2
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)
示例 :
輸入: n = 9
輸出: 45
限制:
1 <= n <= 10000
可以使用的工具:加減法,賦值,位運算符以及邏輯運算符
遞歸
int sumNums(int n){
if(n==0)return 0;
return n+sumNums(n-1);
}
被限制的方向:遞歸的條件判斷
邏輯運算符的短路性質
以邏輯運算符 爲例,對於 這個表達式,如果 A 表達式返回 ,那麼 已經確定爲 ,此時不會去執行表達式 B。同理,對於邏輯運算符 ||, 對於 A || B 這個表達式,如果 A 表達式返回 ,那麼 A || B 已經確定爲 ,此時不會去執行表達式 B。
int sumNums(int n){
n&&(n+=sumNums(n-1));
return n;
}
時間複雜度:
空間複雜度:
函數指針+兩次反運算
typedef unsigned int (*fun)(unsigned int);
unsigned int Teminator(unsigned int n){
return 0;
}
int sumNums(int n){
static fun f[2]={Teminator,sumNums};
return n+f[!!n](n-1); //連續做兩次反運算,非零轉換爲true,0轉換爲false
}
快速乘
使用加法和移位運算
乘以2的冪
遍歷二進制展開
俄羅斯農民乘法
int quickMulti(int A, int B) {
int ans = 0;
for ( ; B; B >>= 1) {
if (B & 1) {
ans += A;
}
A <<= 1;
}
return ans;
}
手動展開 1414 層代替循環
int sumNums(int n){
int ans = 0, A = n, B = n + 1;
(B & 1) && (ans += A);
A <<= 1; //右移一位 乘2
B >>= 1; //左移一位 除2
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
return ans >> 1;
}
回顧
- 遞歸函數的空間複雜度取決於遞歸調用棧的深度
3 新21點
2020-6-3
愛麗絲參與一個大致基於紙牌遊戲 “21點” 規則的遊戲,描述如下:
愛麗絲以 0 分開始,並在她的得分少於 K 分時抽取數字。 抽取時,她從 [1, W] 的範圍中隨機獲得一個整數作爲分數進行累計,其中 W 是整數。 每次抽取都是獨立的,其結果具有相同的概率。當愛麗絲獲得不少於 K 分時,她就停止抽取數字。 愛麗絲的分數不超過 N 的概率是多少?
示例 1:
輸入:N = 10, K = 1, W = 10
輸出:1.00000
示例 2:
輸入:N = 6, K = 1, W = 10
輸出:0.60000
示例 3:
輸入:N = 21, K = 17, W = 10
輸出:0.73278
提示:
0 <= K <= N <= 10000
1 <= W <= 10000
如果答案與正確答案的誤差不超過 10^-5,則該答案將被視爲正確答案通過。
21點:荷官發牌,你的目的是拿到手上的牌總點數儘可能大。不高於某個值(K)的時候你可以一直叫牌,但萬一牌的總點數超過某個值(N)你就爆牌了,此局判負。
那麼本題就是在求贏的概率
遞歸 超時
double new21Game(int N, int K, int W){
double result = 0;
if(K == 0) {
return 1.0;
}
for(int i=1; i<W+1; i++) {
if(i < K) {
double sub = new21Game(N-i, K-i, W);
result += (1/(double)W) * sub;
} else {
if(i <= N) {
result += 1/(double)W;
}
}
}
return result;
}
動態規劃
表示從得分爲 的情況開始遊戲並且獲勝的概率,目標是求 的值
狀態轉移方程:
停止抽牌時,最大牌面爲
double new21Game(int N, int K, int W){
double s=0.0;
double *dp=(double*)malloc((K+W)*sizeof(double));
for(int i = K;i < K + W;i++){
if(i <= N){
dp[i]=1;
}else{
dp[i]=0;
}
s=s+dp[i];
}
for(int i = K-1;i >= 0;i--){
dp[i]=s/W;
s = s-dp[i + W]+dp[i];
}
return dp[0];
}
時間複雜度:
空間複雜度:
4 除自身以外數組的乘積
2020-6-4
給你一個長度爲 n 的整數數組 nums,其中 n > 1,返回輸出數組 output ,其中 output[i] 等於 nums 中除 nums[i] 之外其餘各元素的乘積。
示例:
輸入: [1,2,3,4]
輸出: [24,12,8,6]
提示:題目數據保證數組之中任意元素的全部前綴元素和後綴(甚至是整個數組)的乘積都在 32 位整數範圍內。
說明: 請不要使用除法
當前數左邊乘積*當前數右邊乘積
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* productExceptSelf(int* nums, int numsSize, int* returnSize){
*returnSize=0;
int *L=(int *)malloc(numsSize*sizeof(int));
int *R=(int *)malloc(numsSize*sizeof(int));
int *ans=(int *)malloc(numsSize*sizeof(int));
L[0]=1;
R[numsSize-1]=1;
for(int i=1;i<numsSize;i++){
L[i]=L[i-1]*nums[i-1];
}
for(int i=numsSize-2;i>=0;i--){
R[i]=R[i+1]*nums[i+1];
}
for(int i=0;i<numsSize;i++){
ans[i]=L[i]*R[i];
(*returnSize)++;
}
return ans;
}
時間複雜度:
空間複雜度:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* productExceptSelf(int* nums, int numsSize, int* returnSize){
*returnSize=0;
int *L=(int *)malloc(numsSize*sizeof(int));
L[0]=1;
int R=1;
for(int i=1;i<numsSize;i++){
L[i]=L[i-1]*nums[i-1];
}
for(int i=numsSize-1;i>=0;i--){
L[i]=L[i]*R;
R=R*nums[i];
(*returnSize)++;
}
return L;
}
5 順時針打印矩陣
2020-6-5
輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字。
示例 :
輸入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
輸出:[1,2,3,4,8,12,11,10,9,5,6,7]
限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize){
int m=matrixSize; //行
*returnSize=0;
if(m==0)return NULL;
int n=matrixColSize[0]; //列
int *ans=(int *)malloc((m*n)*sizeof(int));
int top=0,bottom=m-1,left=0,right=n-1;
while(left<=right&&top<=bottom){
for (int column = left; column <= right; column++) { //上
ans[(*returnSize)++] = matrix[top][column];
}
for (int row = top + 1; row <= bottom; row++) { //右
ans[(*returnSize)++] = matrix[row][right];
}
if (left < right && top < bottom) {
for (int column = right - 1; column > left; column--) { //下
ans[(*returnSize)++]= matrix[bottom][column];
}
for (int row = bottom; row > top; row--) { //左
ans[(*returnSize)++] = matrix[row][left];
}
}
left++;
right--;
top++;
bottom--;
}
return ans;
}
時間複雜度:
空間複雜度:
6 最長連續序列
2020-6-6
給定一個未排序的整數數組,找出最長連續序列的長,要求算法的時間複雜度爲 O(n)。
示例:
輸入: [100, 4, 200, 1, 3, 2]
輸出: 4
解釋: 最長連續序列是 [1, 2, 3, 4]。它的長度爲 4。
哈希
- 用哈希表查找,查找的時間複雜度爲
- 鏈表哈希,修改插入函數使之不插入重複數字
- 哈希表直接存入數組中的數字
- 爲減少枚舉,加入跳過判斷條件,即:對於當前數,若不存在,則此爲子序列的第一個數,進入循環,進行對的判斷。由此,數組中的每個數只會進入內層循環一次,則整體的時間複雜度爲
struct hashNode {
int key;
struct hashNode *next;//衝突時,用指針連接
};
struct hashMap {
int size;
struct hashNode *data;
};
struct hashMap * hashCreat(int size)
{
//新建一個哈希表
struct hashMap * hashMapInfo;
//爲哈希表分配地址
hashMapInfo = (struct hashMap *)malloc(sizeof(struct hashMap));
//設置哈希表的大小
hashMapInfo->size = size;
//爲哈希表的data部分分配地址
hashMapInfo->data = (struct hashNode *)malloc(sizeof(struct hashNode) * size);
//初始化哈希表每個哈希節點的對象
for (int i = 0; i < size; i++) {
hashMapInfo->data[i].key = INT_MIN;
hashMapInfo->data[i].next = NULL;
}
return hashMapInfo;
}
//將鍵爲key的節點放入到哈希表中
void Put(int key, struct hashMap * hashMapInfo)
{
//計算節點的位置
int pos = abs(key) % hashMapInfo->size;
//定義哈希節點,其值爲 key所在位置的數據的鏈表地址
struct hashNode *data = &hashMapInfo->data[pos];
//定義一個新的哈希節點,爲空
struct hashNode *newNode = NULL;
if (data->key == INT_MIN) { //鏈表未佔用
data->key = key;
return;
}
while (data != NULL) {//while循環要考慮當前節點,不能從data->next開始
if (data->key == key) { //當前鏈表已存在該key,直接返回不插入
return;
}
if (data->next == NULL) { //爲後續添加Node做準備
break;
}
data = data->next; //鏈表next節點
}
/* 添加節點 */
newNode = (struct hashNode *)malloc(sizeof(struct hashNode));
newNode->key = key;
newNode->next = NULL;
data->next = newNode;
return;
}
//查找
bool Contain(long key, struct hashMap * hashMapInfo)
{
//計算節點的位置
int pos;
if(key==INT_MIN)pos = abs(INT_MAX) % hashMapInfo->size;
else pos = abs(key) % hashMapInfo->size;
//定義哈希節點,其值爲 key所在位置的數據的鏈表地址
struct hashNode *data = &hashMapInfo->data[pos];
if (data->key == INT_MIN) { //鏈表未佔用
return false;
}
while (data != NULL) {//while循環要考慮當前節點,不能從data->next開始
if (data->key == key) { //當前鏈表已存在該key
return true;
}
data = data->next; //鏈表next節點
}
return false;
}
int longestConsecutive(int* nums, int numsSize){
struct hashMap *hashMapInfo;
hashMapInfo=hashCreat(numsSize);
for(int i=0;i < numsSize;i++) {
//printf("TTTT\n");
Put(nums[i], hashMapInfo);
}
int longestStreak = 0;
for (int i=0;i<numsSize;i++) {
if (!Contain(nums[i]-1,hashMapInfo)) { //前一個數不存在,那我此數即爲序列第一個
int currentNum = nums[i];
int currentStreak = 1;
while (Contain(currentNum + 1,hashMapInfo)) {
currentNum += 1;
currentStreak += 1;
}
longestStreak = fmax(longestStreak, currentStreak);
}
}
return longestStreak;
}
並查集
下次一定
回顧
- 使用了空指針
Line 35: Char 37: runtime error: member access within null pointer of type 'struct hashMap' (solution.c)
- 取絕對值越界
Line 72: Char 15: runtime error: negation of -2147483648 cannot be represented in type 'int'; cast to an unsigned type to negate this value to itself (solution.c)
7 等式方程的可滿足性
2020-6-8
給定一個由表示變量之間關係的字符串方程組成的數組,每個字符串方程 equations[i] 的長度爲 4,並採用兩種不同的形式之一:“a==b” 或 “a!=b”。在這裏,a 和 b 是小寫字母(不一定不同),表示單字母變量名。只有當可以將整數分配給變量名,以便滿足所有給定的方程時才返回 true,否則返回 false。
示例 1:
輸入:[“a==b”,“b!=a”]
輸出:false
解釋:如果我們指定,a = 1 且 b = 1,那麼可以滿足第一個方程,但無法滿足第二個方程。沒有辦法分配變量同時滿足這兩個方程。
示例 2:
輸出:[“b==a”,“a==b”]
輸入:true
解釋:我們可以指定 a = 1 且 b = 1 以滿足滿足這兩個方程。
示例 3:
輸入:[“a==b”,“b==c”,“a==c”]
輸出:true
示例 4:
輸入:[“a==b”,“b!=c”,“c==a”]
輸出:false
示例 5:
輸入:[“c==c”,“b==d”,“x!=z”]
輸出:true
提示:
1 <= equations.length <= 500
equations[i].length == 4
equations[i][0] 和 equations[i][3] 是小寫字母
equations[i][1] 要麼是 ‘=’,要麼是 ‘!’
equations[i][2] 是 ‘=’
並查集
#define MAXN 26
int fa[MAXN];
//初始化
void init(int n)
{
for (int i = 0; i < n; ++i){
fa[i] = i;
}
}
//查詢
int find(int x)
{
if(fa[x] == x)
return x;
else
return find(fa[x]);
}
//合併
void merge(int i, int j)
{
fa[find(i)] = find(j);
}
bool equationsPossible(char ** equations, int equationsSize){
init(MAXN);
for(int i=0;i<equationsSize;i++){
if(equations[i][1]=='='){
int index1=equations[i][0]-'a';
int index2=equations[i][3]-'a';
merge(index1,index2);
}
}
for(int i=0;i<equationsSize;i++){
if(equations[i][1]=='!'){
int index1=equations[i][0]-'a';
int index2=equations[i][3]-'a';
if (find(index1) == find(index2)) {
return false;
}
}
}
return true;
}
8 把數字翻譯成字符串
2020-6-9
給定一個數字,我們按照如下規則把它翻譯爲字符串:0 翻譯成 “a” ,1 翻譯成 “b”,……,11 翻譯成 “l”,……,25 翻譯成 “z”。一個數字可能有多個翻譯。請編程實現一個函數,用來計算一個數字有多少種不同的翻譯方法。
示例 1:
輸入: 12258
輸出: 5
解釋: 12258有5種不同的翻譯,分別是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”
提示:
動態規劃
: 爲開頭的數字的翻譯方案數量
從後往前計算
int translateNum(int num){
if(num<0)return 0;
if(num==0)return 1;
int count[11];
int n=0; //位數
//從低位到高位存儲數字中每一位數
int number[11];
int temp=num,i=0;
while(temp){
int complement=temp%10;
temp=temp/10;
n++;
number[i++]=complement;
}
//翻轉,從高位到低位
int Number[11];
for(int i=0;i<n;i++){
Number[i]=number[n-i-1];
}
if(n==1)return 1; //個位數直接輸出
//計算後兩位
count[n-1]=1;
int two=Number[n-2]*10+Number[n-1];
if((two>=10)&&(two<=25))count[n-2]=2;
else count[n-2]=1;
for(int i=n-3;i>=0;i--){
two=Number[i]*10+Number[i+1];
int g;
if((two>=10)&&(two<=25))g=1;
else g=0;
if(i<n-1){
count[i]=count[i+1]+g*count[i+2];
}
}
return count[0];
}
: 爲結尾的數字的翻譯方案數量
初始狀態:
返回值:
int translateNum(int num){
if(num<0)return 0;
if(num==0)return 1;
int count[11];
int n=0; //位數
//從低位到高位存儲數字中每一位數
int number[11];
int temp=num,i=0;
while(temp){
int complement=temp%10;
temp=temp/10;
n++;
number[i++]=complement;
}
//翻轉,從高位到低位
int Number[11];
for(int i=0;i<n;i++){
Number[i]=number[n-i-1];
}
if(n==1)return 1; //個位數直接輸出
count[0]=1;
int two=Number[0]*10+Number[1];
if((two>=10)&&(two<=25))count[1]=2;
else count[1]=1;
for(int i=2;i<n;i++){
int two=Number[i]+Number[i-1]*10;
int g;
if((two>=10)&&(two<=25))g=1;
else g=0;
count[i]=count[i-1]+g*count[i-2];
}
return count[n-1];
}
邊取餘邊計算
: 開頭的數字的翻譯方案數量,從後往前計算
int translateNum(int num){
if(num<0)return 0;
if(num==0)return 1;
int dp0=1; //dp[i]
int dp1=1; //dp[i+1]
int dp2=1; //dp[i+2]
int n1; //第i位
int n2=num%10; //第i+1位
while(num){
num=num/10;
n1=num%10;
int two=10*n1+n2;
if((two>=10)&&(two<=25))dp0=dp1+dp2;
else dp0=dp1;
//向左移動
dp2=dp1;
dp1=dp0;
n2=n1;
}
return dp0;
}
9 迴文數
2020-6-10
判斷一個整數是否是迴文數。迴文數是指正序(從左向右)和倒序(從右向左)讀都是一樣的整數。
示例 1:
輸入: 121
輸出: true
示例 2:
輸入: -121
輸出: false
解釋: 從左向右讀, 爲 -121 。 從右向左讀, 爲 121- 。因此它不是一個迴文數。
轉化爲數組
bool isPalindrome(int x){
if(x==0)return true;
if(x<0)return false;
int num[12],n=0,i=0;
while(x){
num[i]=x%10;
x=x/10;
n++;
i++;
}
for(int j=0,k=n-1;j<=k;j++,k--){
if(num[j]!=num[k])return false;
}
return true;
}
反轉一半數字 厲害
bool isPalindrome(int x){
if(x == 0)return true;
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int revertedNumber = 0;
while (x > revertedNumber) {
revertedNumber = revertedNumber * 10 + x % 10;
x = x / 10;
}
// 當數字長度爲奇數時,我們可以通過 revertedNumber/10 去除處於中位的數字。
// 例如,當輸入爲 12321 時,在 while 循環的末尾我們可以得到 x = 12,revertedNumber = 123,
// 由於處於中位的數字不影響迴文(它總是與自己相等),所以我們可以簡單地將其去除。
return x == revertedNumber || x == revertedNumber / 10;
}
10 迴文鏈表
2020-6-10
請判斷一個鏈表是否爲迴文鏈表,用 O(n) 時間複雜度和 O(1) 空間複雜度
翻轉鏈表+快慢指針
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool isPalindrome(struct ListNode* head){
if(!head || !head->next)
return true;
struct ListNode *fast = head, *slow = head;
struct ListNode *p, *pre = NULL;
while(fast && fast->next){
p = slow;
slow = slow->next; //快慢遍歷
fast = fast->next->next;
p->next = pre; //翻轉
pre = p;
//printf("pre=%d,p=%d,slow=%d,fast=%d\n",pre->val,p->val,slow->val,fast->val);
}
if(fast) //奇數個節點時跳過中間節點
slow = slow->next;
while(p){ //前半部分和後半部分比較
if(p->val != slow->val)
return false;
p = p->next;
slow = slow->next;
}
return true;
}
翻轉鏈表
void reverse(struct ListNode* head){
struct ListNode *h=head;
while(h!=NULL){
printf("%d ",h->val);
h=h->next;
}
printf("\n");
struct ListNode *temp = head;
struct ListNode *p, *pre = NULL;
while(temp!=NULL){
if(pre!=NULL)printf("pre=%d ",pre->val);
if(p!=NULL)printf("p=%d ",p->val);
if(temp!=NULL)printf("temp=%d ",temp->val);
printf("\n");
p = temp;
temp=temp->next;
p->next = pre; //翻轉
pre = p;
}
while(p!=NULL){
printf("%d ",p->val);
p=p->next;
}
while(pre!=NULL){
printf("%d ",pre->val);
pre=pre->next;
}
}
11 每日溫度
2020-6-11
根據每日氣溫列表,請重新生成一個列表,對應位置的輸出是需要再等待多久溫度纔會升高超過該日的天數。如果之後都不會升高,請在該位置用 0 來代替。
例如,給定一個列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的輸出應該是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:氣溫 列表長度的範圍是 [1, 30000]。每個氣溫的值的均爲華氏度,都是在 [30, 100] 範圍內的整數。
暴力超時
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* dailyTemperatures(int* T, int TSize, int* returnSize){
*returnSize=0;
int *ans=(int *)malloc(TSize*sizeof(int));
for(int i=0;i<TSize;i++){
int num=0;
for(int j=i+1;j<TSize;j++){
if(T[j]>T[i]){
num=j-i;
break;
}
}
ans[i]=num;
(*returnSize)++;
}
return ans;
}
時間複雜度:
空間複雜度:
暴力不超時
- 反向遍歷:從後往前依次將各個溫度的下標存入next數組,對於每個溫度,查找next數組中比他大的溫度中角標最小的,相減即爲結果
- 因爲是從後往前,所以查找的一定是當前溫度後面的溫度,√
- 結果數組初始化爲零,當沒有找到比該溫度大的溫度,使用初始值
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* dailyTemperatures(int* T, int TSize, int* returnSize){
*returnSize=0;
int *ans=(int *)malloc(TSize*sizeof(int));
for(int s=0;s<TSize;s++){
ans[s]=0;
}
int next[101]={0};
for(int i=0;i<101;i++){
next[i]+=INT_MAX;
}
for (int i = TSize - 1; i >= 0; --i) {
int warmerIndex = INT_MAX;
for (int t = T[i] + 1; t <= 100; ++t) {
warmerIndex = fmin(warmerIndex, next[t]);
}
if (warmerIndex != INT_MAX) {
ans[i] = warmerIndex - i;
}
next[T[i]] = i;
(*returnSize)++;
}
return ans;
}
時間複雜度:
空間複雜度:
單調棧
- 棧內存下標
- 從棧底到棧頂的下標對應的溫度列表中的溫度依次遞減
- 棧爲空或者當前元素小於棧頂元素,進棧
- 當前元素大於棧頂元素,出棧,計算棧頂元素與當前元素之差即爲所求,一直出棧知道棧頂元素大於當前元素或者棧爲空,進棧
#define MaxSize 30000
typedef struct Stack{
int top;
int num[MaxSize];
}Stack;
Stack S;
void init(){
S.top=0;
}
void push(int a){
S.top++;
S.num[S.top]=a;
}
int pop(){
if(S.top==0)return 0;
S.top--;
return S.num[S.top+1];
}
int Top(){
return S.num[S.top];
}
int empty(){
if(S.top==0)return 1;
else return 0;
}
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* dailyTemperatures(int* T, int TSize, int* returnSize){
init();
*returnSize=0;
int *ans=(int *)malloc(TSize*sizeof(int));
for(int s=0;s<TSize;s++){
ans[s]=0;
}
for(int i=0;i<TSize;i++){
while(!empty()&&T[Top()]<T[i]){
int index=Top();
ans[index]=i-index;
pop();
}
push(i);
(*returnSize)++;
}
return ans;
}
時間複雜度:
空間複雜度:
回顧
我初始化數組時像個人工智障 稍後再看
//數組初始化實在是頭疼
int *ans=(int *)malloc(TSize*sizeof(int));
for(int s=0;s<TSize;s++){
ans[s]=0;
}
int next[101]={0};
for(int i=0;i<101;i++){
next[i]+=INT_MAX;
}
12 四數之和
2020-6-12
三數之和之前搞過了,整個類似的四數之和回憶一下:D
給定一個包含 n 個整數的數組 nums 和一個目標值 target,判斷 nums 中是否存在四個元素 a,b,c 和 d ,使得 a + b + c + d 的值與 target 相等,找出所有滿足條件且不重複的四元組。答案中不可以包含重複的四元組。
示例:
給定數組 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
滿足要求的四元組集合爲:
[[-1, 0, 0, 1],[-2, -1, 1, 2],[-2, 0, 0, 2]]
雙指針
/**
* 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 compInc(const void *a, const void *b) //遞增
{
return *(int *)a - *(int *)b;
}
int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes){
*returnSize = 0; // 參數returnSize用來作爲二維數組行數下標的指針
if (numsSize < 4)
{
return NULL;
}
qsort(nums, numsSize, sizeof(int), compInc);
int maxSize=numsSize*10;
int** returnArray = (int**)malloc(sizeof(int*) * maxSize);
*returnColumnSizes = (int*)malloc(sizeof(int) * maxSize);
for(int i=0;i<numsSize-3;i++){
for(int j=i+1;j<numsSize-2;j++){
int L=j+1,R=numsSize-1;
while(L<R){
int sum=nums[i]+nums[j]+nums[L]+nums[R];
if(sum==target){
returnArray[*returnSize] = (int*)malloc(sizeof(int) * 4);
(*returnColumnSizes)[*returnSize] = 4;
returnArray[*returnSize][0] = nums[i];
returnArray[*returnSize][1] = nums[j];
returnArray[*returnSize][2] = nums[L];
returnArray[*returnSize][3] = nums[R];
(*returnSize)++;
while (nums[L + 1] == nums[L] &&(L+1)< R)
{
L++;
}
while (nums[R - 1] == nums[R] && L < (R-1))
{
R--;
}
L++;
R--;
}
else if(sum<target){
L++;
}
else if(sum>target){
R--;
}
}
while(nums[j]==nums[j+1]&&j<numsSize-2){
j++;
}
}
while(nums[i]==nums[i+1]&&i<numsSize-3){
i++;
}
}
return returnArray;
}
時間複雜度:
13 使用最小花費爬樓梯
2020-6-13
爬樓梯
假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?
int climbStairs(int n){
if(n==1)return 1;
if(n==2)return 2;
int dp1=1,dp2=2,dp=0;
for(int i=3;i<=n;i++){
//printf("dp1= %d dp2= %d dp= %d\n",dp1,dp2,dp);
dp=dp1+dp2;
dp1=dp2;
dp2=dp;
}
return dp;
}
使用最小花費爬樓梯
數組的每個索引作爲一個階梯,第 i個階梯對應着一個非負數的體力花費值 (索引從0開始)。每當你爬上一個階梯你都要花費對應的體力花費值,然後你可以選擇繼續爬一個階梯或者爬兩個階梯。您需要找到達到樓層頂部的最低花費。在開始時,你可以選擇從索引爲 0 或 1 的元素作爲初始階梯。
示例 1:
輸入: cost = [10, 15, 20]
輸出: 15
解釋: 最低花費是從cost[1]開始,然後走兩步即可到階梯頂,一共花費15。
示例 2:
輸入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
輸出: 6
解釋: 最低花費方式是從cost[0]開始,逐個經過那些1,跳過cost[3],一共花費6。
注意:
cost 的長度將會在 [2, 1000]。
每一個 cost[i] 將會是一個Integer類型,範圍爲 [0, 999]。
動態規劃
表示從第個階梯開始爬並經過第個階梯,需要的最小花費
int minCostClimbingStairs(int* cost, int costSize){
int dp1 = 0, dp2 = 0;
for (int i = costSize - 1; i >= 0; --i) {
int dp0 = cost[i] + fmin(dp1, dp2);
dp2 = dp1;
dp1 = dp0;
}
return fmin(dp1, dp2);
}
表示第 級階梯的總花費 = 第 級的 + 前兩級階梯的總花費的較小者
//dp[i] = cost[i] + min(dp[i-1], dp[i-2])
int minCostClimbingStairs(int* cost, int costSize){
int dp1 = 0, dp2 = 0;
for (int i = 0; i < costSize; i++) {
int dp0 = cost[i] + fmin(dp1, dp2);
dp2 = dp1;
dp1 = dp0;
}
return fmin(dp1, dp2);
}
初始可以選擇第0或第1個階梯,所以,題目就是要求出
int minCostClimbingStairs(int* cost, int costSize){
int *dp=(int *)malloc((costSize+1)*sizeof(int));
dp[0]=0;
dp[1]=0;
for (int i = 2; i < costSize+1; i++) {
dp[i]= fmin(dp[i-2]+ cost[i-2], dp[i-1] + cost[i-1]);
}
return dp[costSize];
}
14 最長公共前綴
2020-6-15
編寫一個函數來查找字符串數組中的最長公共前綴。
如果不存在公共前綴,返回空字符串 “”。
示例 1:
輸入: [“flower”,“flow”,“flight”]
輸出: “fl”
示例 2:
輸入: [“dog”,“racecar”,“car”]
輸出: “”
解釋: 輸入不存在公共前綴。
char * longestCommonPrefix(char ** strs, int strsSize){
int n=10000;
for(int i=0;i<strsSize;i++){
if(n>strlen(strs[i]))n=strlen(strs[i]);
}
char *ans=(char *)malloc((n+1)*sizeof(char));
int s=0;
if(strsSize==0)
{
ans[s]='\0';
return ans;
}
for(int j=0;j<n;j++){
for(int k=0;k<strsSize-1;k++){
if(strs[k][j]!=strs[k+1][j]){
ans[s]='\0';
return ans;
}
}
ans[s]=strs[0][j];
s++;
}
ans[s]='\0';
return ans;
}
官方題解給了四種方法:縱向、橫向、分治、二分,但從解題角度來講,縱向就挺好,又快。還有一個字典樹,下次一定。
15 二叉樹的序列化與反序列化
2020-6-16
- 節點數值之間用逗號隔開
遞歸 前序遍歷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/** Encodes a tree to a single string. */
#define MAX_SIZE 100000
#define STR_SIZE 10
void pre_order(struct TreeNode *root, char * data){ //前序遍歷
if(root == NULL){
strcat(data, "#");
strcat(data, ",");
return;
}
char tmp[STR_SIZE] = "";
sprintf(tmp, "%d", root->val); // 把結果輸出到字符串tmp中
strcat(data, tmp);
strcat(data, ",");
pre_order(root->left, data);
pre_order(root->right, data);
}
char* serialize(struct TreeNode* root) {
char * ans = malloc(sizeof(char) * MAX_SIZE);
memset(ans, '\0', sizeof(char) * MAX_SIZE);
pre_order(root, ans);
return ans;
}
struct TreeNode * createTree(char *data, int *index){
if(data[*index] == '#'){
(*index)++; // ‘#’
(*index)++; // ‘,’
return NULL;
}
struct TreeNode * root = malloc(sizeof(struct TreeNode));
char tmp[STR_SIZE] = "";
int k = 0;
while(data[*index] != ','){
tmp[k++] = data[*index];
(*index)++;
}
(*index)++; // 因爲此時data[*index] = ','
root->val = atoi(tmp); // 獲取數值
root->left = createTree(data, index); // 遞歸左子樹
root->right = createTree(data, index); // 遞歸右子樹
return root;
}
/** Decodes your encoded data to tree. */
struct TreeNode* deserialize(char* data) {
int index = 0; // index表示數組下標,不需要回溯
return createTree(data, &index);
}
// Your functions will be called as such:
// char* data = serialize(root);
// deserialize(data);
序列化 層序遍歷 非遞歸 隊列
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/** Encodes a tree to a single string. */
#define MaxSize 100000
typedef struct queue {
struct TreeNode* data[MaxSize];
int front;
int rear;
}Queue;
void initilize(Queue *Q) {
Q->front = 0;
Q->rear = 0;
}
void Push(struct TreeNode* root,Queue *Q) {
Q->data[++Q->rear] = root;
}
struct TreeNode* Pop(Queue *Q) {
return Q->data[++Q->front];
}
struct TreeNode* Top(Queue *Q) {
return Q->data[Q->front];
}
int empty(Queue *Q) {
return Q->rear == Q->front;
}
char* serialize(struct TreeNode* root) {
Queue * Q=(Queue*)malloc(sizeof(Queue));
initilize(Q);
struct TreeNode *cur= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
char *ans=(char *)malloc(MaxSize*sizeof(char));
ans[0]='\0'; ////爲方便使用strcat
char tmp[10] = "";
if (root == NULL) return 0; //空樹
Push(root,Q);
sprintf(tmp, "%d", root->val);
strcat(ans, tmp);
strcat(ans, ",");
while (!empty(Q)) {
cur = Pop(Q);
if(cur->left == NULL){
strcat(ans, "*");
strcat(ans, ",");
}else{
sprintf(tmp, "%d", cur->left->val);
strcat(ans, tmp);
strcat(ans, ",");
Push(cur->left,Q);
}
if(cur->right == NULL){
strcat(ans, "*");
strcat(ans, ",");
}else{
sprintf(tmp, "%d", cur->right->val);
strcat(ans, tmp);
strcat(ans, ",");
Push(cur->right,Q);
}
}
//輸出轉換後的序列
int t=0;
while(ans[t]!='\0'){
printf("%c ",ans[t]);
t++;
}
return ans;
}
16 最長公共前綴
2020-6-17
給定正整數數組 ,表示第個觀光景點的評分,並且兩個景點$ i $和 之間的距離爲 。一對景點組成的觀光組合的得分爲:景點的評分之和減去它們兩者之間的距離。返回一對觀光景點能取得的最高分。
示例:
輸入:[8,1,5,2,6]
輸出:11
解釋:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11
提示:
2 <= A.length <= 50000
1 <= A[i] <= 1000
- 次序問題,一定在之前,則對的計算可以與同時進行
- 對於每一個,其對應的中的一定是在之前,因此可以在迭代的同時計算最大的,可以保證對於每一個,最大的都是在之前的數
- 有點前綴和的意思
int maxScoreSightseeingPair(int* A, int ASize){
int ans = 0, mx = A[0] + 0;
for (int j = 1; j < ASize; ++j) {
ans = fmax(ans, mx + A[j] - j);
mx = fmax(mx, A[j] + j);
}
return ans;
}
17 最長公共前綴
2020-6-18
我們從二叉樹的根節點 root 開始進行深度優先搜索。
在遍歷中的每個節點處,我們輸出 D 條短劃線(其中 D 是該節點的深度),然後輸出該節點的值。(如果節點的深度爲 D,則其直接子節點的深度爲 D + 1。根節點的深度爲 0)。
如果節點只有一個子節點,那麼保證該子節點爲左子節點。
給出遍歷輸出 S,還原樹並返回其根節點 root。
迭代 棧
- 從前向後依次遍歷
- 計算當前節點的層數與數值
- 當節點的層數等於當前棧的長度,則當前節點爲棧頂元素的左孩子
- 如果節點的層數不等於當前棧的長度,出棧直到等於,且當前節點爲棧頂元素的右孩子
- 每個節點都在最後入棧
- 最後出棧直到只剩第一個元素,即爲根節點,返回即可
循環 | level | value | length | pop | action | push | stack |
---|---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | - | - | 1 | 1 |
2 | 1 | 2 | 1 | - | 2 | 1 2 | |
3 | 2 | 3 | 2 | - | 3 | 1 2 3 | |
4 | 2 | 4 | 3 | 3 | 4 | 1 2 4 | |
5 | 1 | 5 | 3 | 4 2 | 5 | 1 5 | |
6 | 2 | 6 | 2 | - | 6 | 1 5 6 | |
7 | 2 | 7 | 1 | 6 | 7 | 1 5 7 | |
8 | - | - | - | 7 5 | - | - | 1 |
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
#define MaxSize 100000
typedef struct Stack {
struct TreeNode* data[MaxSize];
int top;
}Stack;
Stack S;
void Initilize() {
S.top=0;
}
void Push(struct TreeNode* root) {
S.top++;
S.data[S.top]=root;
}
struct TreeNode* Pop() {
S.top--;
return S.data[S.top+1];
}
struct TreeNode* Top() {
return S.data[S.top];
}
int Empty() {
return S.top==0;
}
int Length(){
return S.top;
}
struct TreeNode* recoverFromPreorder(char * S){
int n=strlen(S);
Initilize();
for(int i=0;i<n;){
int level=0;
while(S[i]=='-'){ //計算每一個節點的深度
i++;
level++;
}
// printf("level: %d ",level);
int value=0;
while(i<n&&isdigit(S[i])){ //計算節點的數字,需要逐位翻譯
value=value*10+(int)(S[i]-'0');
i++;
}
//printf("value: %d ",value);
struct TreeNode* node=malloc(sizeof(struct TreeNode));
node->val=value;
node->left=NULL;
node->right=NULL;
//printf("length: %d ",Length());
if(level==Length()){
if(!Empty()){
Top()->left=node;
}
}else{
while(level!=Length()){
Pop();
}
Top()->right=node;
}
Push(node);
}
while(Length()>1){
Pop();
}
return Top();
}
18 驗證迴文串
2020-6-19
給定一個字符串,驗證它是否是迴文串,只考慮字母和數字字符,可以忽略字母的大小寫。
說明:本題中,我們將空字符串定義爲有效的迴文串。
示例 1:
輸入: “A man, a plan, a canal: Panama”
輸出: true
示例 2:
輸入: “race a car”
輸出: false
雙指針
bool isPalindrome(char * s){
int n=strlen(s);
//printf("%d",n);
if(n==0)return true;
for(int i=0;i<n;i++){
if(s[i]>='A'&&s[i]<='Z'){
s[i]=s[i]+32;
printf("%c ",s[i]);
}
}
for(int left=0,right=n-1;left<right;left++,right--){
//printf("left: %d,right: %d \n",left,right);
while(!((s[left]>='0'&&s[left]<='9')||(s[left]>='a'&&s[left]<='z'))&&left<right){
left++;
}
while(!((s[right]>='0'&&s[right]<='9')||(s[right]>='a'&&s[right]<='z'))&&left<right){
right--;
}
if(s[left]!=s[right]){
return false;
}
}
return true;
}
特殊的測試用例
"" //空字符串
",.," //不含有數字字母的字符串
回顧
- 官方題解說:“本題考查的是語言中常用字符(串)相關 API 的使用”,使用C語言的我留下不學無術的淚水
- 迴文的常見的3種做法:雙指針,棧,reverse
19 二叉樹中的最大路徑和
2020-6-21
給定一個非空二叉樹,返回其最大路徑和。
本題中,路徑被定義爲一條從樹中任意節點出發,達到任意節點的序列。該路徑至少包含一個節點,且不一定經過根節點。
遞歸
- 只有當前節點
- 當前節點+左節點
- 當前節點+由節點
- 當前節點+左節點+右節點
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
int maxGain(struct TreeNode *root,int *maxValue){
if(root==NULL){
return 0;
}
int leftGain=fmax(maxGain(root->left,maxValue),0);
int rightGain=fmax(maxGain(root->right,maxValue),0);
int curValue=root->val+leftGain+rightGain;
//printf("curValue: %d maxValue: %d\n",curValue,*maxValue);
*maxValue=fmax(*maxValue,curValue);
return root->val+fmax(leftGain,rightGain);
}
int maxPathSum(struct TreeNode* root)
{
int maxValue = INT_MIN; // 最小值
maxGain(root, &maxValue);
return maxValue;
}
20 二進制加法
2020-6-23
給你兩個二進制字符串,返回它們的和(用二進制表示)。
輸入爲 非空 字符串且只包含數字 1 和 0。
示例 1:
輸入: a = “11”, b = “1”
輸出: “100”
示例 2:
輸入: a = “1010”, b = “1011”
輸出: “10101”
模擬加法
- 翻轉字符串
- 取 ,循環 次,從最低位開始遍歷。
- 表示上一個位置的進位,初始值爲 0。
- 記當前位置對其的兩個位爲 和
- 每一位的答案爲 ,下一位的進位爲
- 循環計算直到數字 a和 b 的每一位計算完畢
- 若 的不爲0,則添加1到末尾。
- 翻轉字符串
//翻轉字符串
void reserve(char* s) {
int len = strlen(s);
for (int i = 0; i < len / 2; i++) {
char t = s[i];
s[i] = s[len - i - 1], s[len - i - 1] = t;
}
}
char* addBinary(char* a, char* b) {
reserve(a);
reserve(b);
int len_a = strlen(a), len_b = strlen(b);
int n = fmax(len_a, len_b), carry = 0, len = 0;
char* ans = (char*)malloc(sizeof(char) * (n + 2));
for (int i = 0; i < n; ++i) {
carry += i < len_a ? (a[i] == '1') : 0;
carry += i < len_b ? (b[i] == '1') : 0;
ans[len++] = carry % 2 + '0';
carry /= 2;
}
if (carry) {
ans[len++] = '1';
}
ans[len] = '\0'; //重要操作
reserve(ans);
return ans;
}
位運算
- ans = x ^ y:計算當前 x 和 y 的無進位相加結果
- carry = (x & y) << 1:計算當前 x 和 y 的進位
- 循環直到進位爲零
半成品代碼
void reserve(char* s) {
int len = strlen(s);
printf("len: %d ",len);
for (int i = 0; i < len / 2; i++) {
char t = s[i];
s[i] = s[len - i - 1];
s[len - i - 1] = t;
}
}
char * addBinary(char * a, char * b){
int n1=strlen(a);
int n2=strlen(b);
int A=0;
int B=0;
int ans,carry;
//字符串轉化爲整數
for(int i=0;i<n1;i++){
A=A*10+a[ i ]-'0';
}
for(int i=0;i<n2;i++){
B=B*10+b[ i ]-'0';
}
while(B){
ans=(A ^ B);
carry=(A & B) << 1;
A=ans;
B=carry;
}
//整數轉化爲字符串
int i=0;
char* Ans=malloc((fmax(n1,n2)+2)*sizeof(char));
while(ans != 0){
Ans[ i ]=ans % 10+'0';
i++;
ans=ans/10;
}
Ans[i]='\0'; //重要操作
reserve(Ans);
return Ans;
}
21 最接近的三數之和
2020-6-24
給定一個包括 n 個整數的數組 nums 和 一個目標值 target。找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在唯一答案。
示例:
輸入:nums = [-1,2,1,-4], target = 1
輸出:2
解釋:與 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
雙指針
與三數之和相同
int compInc(const void *a, const void *b) //遞增
{
return *(int *)a - *(int *)b;
}
int threeSumClosest(int* nums, int numsSize, int target){
int error=INT_MAX,ans;
qsort(nums, numsSize, sizeof(int), compInc);
for(int i=0;i<numsSize;i++){
int L=i+1;
int R=numsSize-1;
while(L<R){
int sum=nums[i]+nums[L]+nums[R];
if(sum==target){
return target;
}
if(fabs(sum-target)<error){
error=fabs(sum-target);
ans=sum;
}
if(sum<target){
while(nums[L]==nums[L+1]&&(L+1)<R){
L++;
}
L++;
}else{
while(nums[R]==nums[R-1]&&L<(R-1)){
R--;
}
R--;
}
}
}
return ans;
}
22 缺失的第一個正數
2020-6-27
給你一個未排序的整數數組,請你找出其中沒有出現的最小的正整數。
示例 1: 輸入: [1,2,0] 輸出: 3
示例 2: 輸入: [3,4,-1,1] 輸出: 2
示例 3: 輸入: [7,8,9,11,12] 輸出: 1
提示:
你的算法的時間複雜度應爲O(n),並且只能使用常數級別的額外空間。
關鍵思路
對於一個長度爲 的數組,其中沒有出現的最小正整數只能在 中。這是因爲如果 都出現了,那麼答案是 ,否則答案是 中沒有出現的最小正整數
變相哈希
- 數據處理,負數不予考慮,轉換爲一個較大的數,比如n+1
- 建立一個長度爲N的哈希數組分別存放1到N
- 遍歷原數組,若值小於等於N,則將哈希數組中的此值劃掉(取負)
- 只取一次負
- 遍歷哈希數組,出現的第一個沒有被劃掉的數即爲所求,若所有的都被劃掉,則結果爲N+1
我的抄答案版本
int firstMissingPositive(int* nums, int numsSize){
int* NUM=malloc(numsSize*sizeof(int));
for(int i=0;i<numsSize;i++){
NUM[i]=i+1;
}
for(int i=0;i<numsSize;i++){
if(nums[i]<=0){
nums[i]=numsSize+1;
}
}
for(int j=0;j<numsSize;j++){
if(nums[j]<=numsSize&&NUM[nums[j]-1]>0){
NUM[nums[j]-1]=-NUM[nums[j]-1];
}
}
for(int k=0;k<numsSize;k++){
if(NUM[k]>0)return NUM[k];
}
return numsSize+1;
}
官方題解版本,不新建數組
- 利用角標的關係:數組中的數小於N時,將當前的數作爲角標,此角標對應的數取負,則最後的結果數組中沒有被取負的數對應的角標即爲所求,都取負爲N+1
- 在比較當前數與N的大小關係時需要先取絕對值,因爲可能會在之前的操作中已經被取負
int firstMissingPositive(int* nums, int numsSize) {
for (int i = 0; i < numsSize; ++i) {
if (nums[i] <= 0) {
nums[i] = numsSize + 1;
}
}
for (int i = 0; i < numsSize; ++i) {
int num = abs(nums[i]);
if (num <= numsSize) {
nums[num - 1] = -abs(nums[num - 1]);
}
}
for (int i = 0; i < numsSize; ++i) {
if (nums[i] > 0) {
return i + 1;
}
}
return numsSize + 1;
}
置換
-
對數組進行一次遍歷,對於遍歷到的數 ,如果 ,我們就知道 應當出現在數組中的 的位置,因此交換 和 ,這樣 就出現在了正確的位置。在完成交換後,新的 可能還在 的範圍內,我們需要繼續進行交換操作,直到
-
如果 恰好與 相等,那麼就會無限交換下去。此時我們有 ,說明 已經出現在了正確的位置。因此我們可以跳出循環,開始遍歷下一個數。
-
由於每次的交換操作都會使得某一個數交換到正確的位置,因此交換的次數最多爲 ,整個方法的時間複雜度爲
int firstMissingPositive(int* nums, int numsSize) {
for (int i = 0; i < numsSize; ++i) {
while (nums[i] > 0 && nums[i] <= numsSize &&
nums[nums[i] - 1] != nums[i]) {
int t = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i], nums[i] = t;
}
}
for (int i = 0; i < numsSize; ++i) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return numsSize + 1;
}
給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s 的長度最小的連續子數組,並返回其長度。如果不存在符合條件的連續子數組,返回 0。
示例:
輸入: s = 7, nums = [2,3,1,2,4,3]
輸出: 2
解釋: 子數組 [4,3] 是該條件下的長度最小的連續子數組。
23 長度最小的子數組
2020-6-28
暴力
int minSubArrayLen(int s, int* nums, int numsSize){
if(numsSize==0)return 0;
int ans=INT_MAX;
for(int i=0;i<numsSize;i++){
int sum=0;
for(int j=i;j<numsSize;j++){
sum=sum+nums[j];
int count=j-i+1;
printf("sum= %d count= %d\n",sum,count);
if(sum>=s&&count<ans){
ans=count;
break;
}
}
}
if(ans==INT_MAX)ans=0;
return ans;
}
時間複雜度:
空間複雜度:
雙指針
int minSubArrayLen(int s, int* nums, int numsSize) {
if (numsSize == 0) {
return 0;
}
int ans = INT_MAX;
int L=0,R=0,sum=0;
while(R<numsSize){
sum+=nums[R];
while(sum>=s){
ans=fmin(ans,R-L+1);
sum-=nums[L];
L++;
}
R++;
}
return ans == INT_MAX ? 0 : ans;
}
時間複雜度:
空間複雜度:
前綴和+二分查找
- 確定每個子數組的開始下標後,使用二分查找,找到長度最小的子數組需要 的時間
- 數組中每個元素都爲正,所以前綴和一定是遞增的,這一點保證了二分的正確性
int lower_bound(int *a, int l, int r, int q) {
if (a[r] < q) return -1;
while (l < r) {
int mid = (l + r) >> 1;
if (a[mid] >= q) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
int minSubArrayLen(int s, int *nums, int numsSize) {
if (numsSize == 0) {
return 0;
}
int ans = INT_MAX;
int *sums = (int *)malloc(sizeof(int) * (numsSize + 1));
// 爲了方便計算,令 size = n + 1
// sums[0] = 0 意味着前 0 個元素的前綴和爲 0
// sums[1] = A[0] 前 1 個元素的前綴和爲 A[0]
// 以此類推
for (int i = 1; i <= numsSize; i++) {
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 1; i <= numsSize; i++) {
int target = s + sums[i - 1];
int bound = lower_bound(sums, 1, numsSize, target);
if (bound != -1) {
ans = fmin(ans, bound - (i - 1));
}
}
return ans == INT_MAX ? 0 : ans;
}
時間複雜度:
空間複雜度:
24 數組中的第K個最大元素
2020-6-29
在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。
示例 1:
輸入: [3,2,1,5,6,4] 和 k = 2
輸出: 5
示例 2:
輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4
輸出: 4
說明:
你可以假設 k 總是有效的,且 1 ≤ k ≤ 數組的長度。
暴力冒泡排序
int findKthLargest(int* nums, int numsSize, int k){
for(int i=numsSize-1;i>=numsSize-k;i--){
for(int j=0;j<i;j++){
if(nums[j]>nums[j+1]){
int t=nums[j];
nums[j]=nums[j+1];
nums[j+1]=t;
}
}
}
return nums[numsSize-k];
}
暴力快排超時
//嚴奶奶版本快排
int Partition(int *A,int low,int high){
int key=A[low];
while(low < high){
while(low < high && A[high] >= key){
high--;
}
int t=A[low];
A[low]=A[high];
A[high]=t;
while(low < high && A[low] <= key){
low++;
}
t=A[low];
A[low]=A[high];
A[high]=t;
}
A[low]=key;
return low;
}
void QSort(int* A,int low,int high){
if(low<high){
int piv=Partition(A,low,high);
QSort(A,low,high-1);
QSort(A,low+1,high);
}
}
int findKthLargest(int* nums, int numsSize, int k){
QSort(nums,0,numsSize-1);
for(int i=0;i < numsSize;i++){
printf("%d ",nums[i]);
}
return nums[numsSize-k];
}
快速選擇排序
- 對於快速排序,每一趟排序之後,作爲樞軸的關鍵字就被交換到正確的位置k,在他左邊是小於他的數,右邊是大於他的數,此關鍵字就是第K大的數字
- 因此可以在每一趟排序之後判斷此關鍵字的角標k與K的大小關係,若k小於K,則快排右半部分,若k大於K,則快排左半部分
//算法導論版本快排
int Partition(int* a, int l, int r) {
int x = a[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
int t = a[++i];
a[i] = a[j], a[j] = t;
}
}
int t = a[i + 1];
a[i + 1] = a[r], a[r] = t;
return i + 1;
}
int quickSelect(int* a, int l, int r, int index) {
int q = Partition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index): quickSelect(a, l, q - 1, index);
}
}
int findKthLargest(int* nums, int numsSize, int k) {
return quickSelect(nums, 0, numsSize - 1, numsSize - k);
}
快速排序隨機化版本
隨機選擇主元:隨機選擇一個元素與A[r]交換
inline int partition(int* a, int l, int r) {
int x = a[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
int t = a[++i];
a[i] = a[j], a[j] = t;
}
}
int t = a[i + 1];
a[i + 1] = a[r], a[r] = t;
return i + 1;
}
inline int randomPartition(int* a, int l, int r) {
int i = rand() % (r - l + 1) + l;
int t = a[i];
a[i] = a[r], a[r] = t;
return partition(a, l, r);
}
int quickSelect(int* a, int l, int r, int index) {
int q = randomPartition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index)
: quickSelect(a, l, q - 1, index);
}
}
int findKthLargest(int* nums, int numsSize, int k) {
srand(time(0));
return quickSelect(nums, 0, numsSize - 1, numsSize - k);
}
堆排序
void maxHeapify(int* a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest]) {
largest = l;
}
if (r < heapSize && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
int t = a[i];
a[i] = a[largest], a[largest] = t;
maxHeapify(a, largest, heapSize);
}
}
void buildMaxHeap(int* a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
int findKthLargest(int* nums, int numsSize, int k) {
int heapSize = numsSize;
buildMaxHeap(nums, heapSize);
for (int i = numsSize - 1; i >= numsSize - k + 1; --i) {
int t = nums[0];
nums[0] = nums[i], nums[i] = t;
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
- 時間複雜度:,建堆的時間代價是 ,刪除的總代價是 ,因爲 ,故漸進時間複雜爲
- 空間複雜度:,即遞歸使用棧空間的空間代價。
25 用兩個棧實現隊列
2020-6-30
用兩個棧實現一個隊列。隊列的聲明如下,請實現它的兩個函數 appendTail 和 deleteHead ,分別完成在隊列尾部插入整數和在隊列頭部刪除整數的功能。(若隊列中沒有元素,deleteHead 操作返回 -1 )
示例 1:
輸入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
輸出:[null,null,3,-1]
示例 2:
輸入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
輸出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多會對 appendTail、deleteHead 進行 10000 次調用
#define MaxSize 10000
typedef struct {
int data1[MaxSize];
int data2[MaxSize];
int top1;
int top2;
} CQueue;
CQueue* cQueueCreate() {
CQueue* obj=malloc(sizeof(CQueue));
obj->top1=0;
obj->top2=0;
return obj;
}
void cQueueAppendTail(CQueue* obj, int value) {
obj->top1++;
obj->data1[obj->top1]=value;
}
int cQueueDeleteHead(CQueue* obj) {
if(obj->top2!=0){
obj->top2--;
return obj->data2[obj->top2+1];
}
while(obj->top1!=0){
//出棧
int t=obj->data1[obj->top1];
obj->top1--;
//進棧
obj->top2++;
obj->data2[obj->top2]=t;
}
if(obj->top2==0)return -1;
obj->top2--;
return obj->data2[obj->top2+1];
}
void cQueueFree(CQueue* obj) {
free(obj);
obj = NULL;
}
/**
* Your CQueue struct will be instantiated and called as such:
* CQueue* obj = cQueueCreate();
* cQueueAppendTail(obj, value);
* int param_2 = cQueueDeleteHead(obj);
* cQueueFree(obj);
*/
基礎知識
sprintf
函數功能:格式化字符串,將格式化的數據寫入字符串中。
函數原型:
int sprintf(char *buffer, const char *format, [argument]...)
// buffer:是char類型的指針,指向寫入的字符串指針
// format:格式化字符串,即在程序中想要的格式
// argument:可選參數,可以爲任意類型的數據
函數返回值:buffer指向的字符串的長度;
strcat 字符串連接
頭文件:
#include<string.h>
#include<cstring>
聲明:
char *strcat(char *dest, const char *src)
參數:
- dest – 指向目標字符串,該數組包含了一個 C 字符串,且足夠容納追加後的字符串
- src – 指向要追加的字符串,該字符串不會覆蓋目標的字符串
功能:
把src所指字符串添加到dest結尾處(覆蓋dest結尾處的’\0’)並添加’\0’