2020年5月 leetcode每日一題 C語言版本

1 序列和

2020-4-27

題目描述

給出一個正整數N和長度L,找出一段長度大於等於L的連續非負整數,他們的和恰好爲N。答案可能有多個,我們需要找出長度最小的那個。
例如 N = 18 L = 2:
5 + 6 + 7 = 18
3 + 4 + 5 + 6 = 18
都是滿足要求的,但是我們輸出更短的 5 6 7

輸入描述:
輸入數據包括一行: 兩個正整數N(1 ≤ N ≤ 1000000000),L(2 ≤ L ≤ 100)

輸出描述:
從小到大輸出這段連續非負整數,以空格分隔,行末無空格。如果沒有這樣的序列或者找出的序列長度大於100,則輸出No

示例

輸入

18 2

輸出

5 6 7

解題思路:
等差數列前n項和Sn=n(a1+an)2=n(a1+a1+(n1)d)2S_n=\frac{n(a_1+a_n)}{2}=\frac{n(a_1+a_1+(n-1)d)}{2}
d=1d=1時,Sn=n(a1+an)2=n(2a1+n1)2=NS_n=\frac{n(a_1+a_n)}{2}=\frac{n(2a_1+n-1)}{2}=N

a1=2N+nn22na_1=\frac{2N+n-n^2}{2n}
其中,a1a_1是序列開始的值,n是序列的長度,則可以對序列的長度從L到100循環,如果對應的n代入上式求得的a1a_1爲整數,則滿足條件,且第一個滿足條件的就是長度最小的那組。

C代碼:

#include<stdio.h>
#include<iostream>
using namespace std;

int MinSum(int N, int L)
{
    for (int i = L; i <= 100; i++)
    {
        if ((2 * N + i - i * i) % (2 * i) == 0&&(2*N-i*(i-1))/(2*i)>=0)
        {
            int s = (2 * N + i - i * i) / (2 * i);
            for (int j = 0; j < i-1; j++) 
                cout << s+j << " ";
            cout<<s+i-1;//保證行末無空格
            return 0;
        }
    }   
    cout << "No";
    return 0;
}

int main()
{
    int N, L;
    cin >> N >>L;
    //cout << N << L;
    MinSum(N,L);
    return 0;
}

回顧

  1. 善於使用取商與取餘
  2. 選取最小的一組不一定要找出所有的情況再選擇,當情況較多時考慮曲線救國
  3. 注意“行末無空格”的細節輸出處理

2 兩數相加

2020-4-28

給出兩個非空的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照逆序的方式存儲的,並且它們的每個節點只能存儲一位數字。我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。

示例:
輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807

我的VS運行版本:

#include<stdio.h>
#include<iostream>
using namespace std;

struct ListNode {
    int val;   //數據域
    struct ListNode* next;   //指針域,數據類型爲指針
};

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2)
{
    struct ListNode* head = (ListNode*) calloc(1, sizeof(ListNode));
    struct ListNode* cur = head;

    int carry = 0,x=0,y=0,sum=0,k=0;    //進位carry初始化爲0
    ListNode* p = l1;  //將p q分別初始化爲列表的頭部
    ListNode* q = l2;

    //若其中一個鏈表爲空,則直接返回另一個列表
    if (!p)
        return q;
    if (!q)
        return p;

    while (p||q||carry==1)
    {
        if (p)
        {
            x = p->val;
            p = p->next;
        }
        else
            x = 0;
        if (q)
        {
            y = q->val;
            q = q->next;
        }
        else
            y = 0;
        
        sum = x + y + carry;
        carry = sum / 10; //商
        k= sum %10;  //餘數

        cur->next= (ListNode*)calloc(1, sizeof(ListNode));
        cur->next->val = k;
        cur = cur->next; 
    }
    cur->next = NULL;
    return head;
}

struct ListNode* initList(int n) 
{
    ListNode* p = (ListNode*)malloc(sizeof(ListNode));//創建一個頭結點
    ListNode* temp = p;//聲明一個指針指向頭結點,
    int key;
    for (int i = 0; i < n; i++) {
        ListNode* a = (ListNode*)malloc(sizeof(ListNode));//申請一個新節點
        cin >> key;
        a->val = key;
        a->next = NULL;
        temp->next = a;
        temp = temp->next;
    }
    return p->next;
}

void display(ListNode* p)
{
    ListNode* temp = p;//將temp指針重新指向頭結點
    while (temp->next) 
    {
        temp = temp->next;
        printf("%d", temp->val);
    }
    printf("\n");
}

int main()
{
    int m, n;   
    cin >> m;
    ListNode* l1 = initList(m);
    cin >> n;
    ListNode* l2 = initList(n);
    ListNode* l = addTwoNumbers(l1, l2);
    display(l);
}

leetcode AC版本:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2)
{
    struct ListNode* head = (struct ListNode*) calloc(1, sizeof(struct ListNode));
    struct ListNode* cur = head;
    
    struct ListNode* p = l1;  //將p q分別初始化爲列表的頭部
    struct ListNode* q = l2;
    int carry = 0,x=0,y=0,sum=0,k=0;    //進位carry初始化爲0
    
    if (!p)
        return q;
    if (!q)
        return p;

    while (p||q||carry==1)
    {
        if (p)
        {
            x = p->val;
            p = p->next;
        }
        else
            x = 0;
        if (q)
        {
            y = q->val;
            q = q->next;
        }
        else
            y = 0;
        sum = x + y + carry;
        carry = sum / 10; //商
        k= sum %10;  //餘數

        cur->next= (struct ListNode*)calloc(1, sizeof(struct ListNode));
        cur->next->val = k;
        cur = cur->next; 
    }
    cur->next = NULL;
    return head->next;
}

在這裏插入圖片描述

回顧:

  1. 在不輸入鏈表長度情況下輸入鏈表(存疑)
  2. 爲何在我的VS中使用ListNode*就可以聲明指針,而在LeetCode中需要加struct(存疑)
  3. 需要考慮的細節:首位進位問題、兩列表長度不等時的情況;
  4. LeetCode版本中結構體的初始化是沒有定義頭結點,所以head->next表示第一個結點,而在VS版本中head即第一個結點

3 刪除鏈表的倒數第N個節點

2020-4-29

題目描述:
給定一個鏈表,刪除鏈表的倒數第 n 個節點,並且返回鏈表的頭結點。
示例:
給定一個鏈表: 1->2->3->4->5, 和 n = 2.
當刪除了倒數第二個節點後,鏈表變爲 1->2->3->5.

leetcode AC版本:

/**
 * Definition for singly-linked list.
 struct ListNode {
      int val;
      struct ListNode *next;
 };*/
 
int count(struct ListNode* L)
{
    struct ListNode* p = (struct ListNode*)calloc(1, sizeof(struct ListNode));
    p = L;
    int i = 0;
    do {
        i++;
        p = p->next
    } while (p);
    return i;
}

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    int length=count(head);
    struct ListNode* dummyHead = (struct ListNode*) calloc (1, sizeof(struct ListNode));
    dummyHead=head;
    int i=1;
    while(head)
    {      
        if(length-n==0)
        {
            dummyHead=head->next;
            break;
        }
        if(i==(length-n))
        {
            head->next=head->next->next;
            break;
        }
        i++;
        head=head->next;
    }
    return dummyHead; 
}

在這裏插入圖片描述
結合題解修改之後的版本

int count(struct ListNode* L)
{
    int i = 0;
    do {
        i++;
        L = L->next;
    } while (L);
    return i;
}

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    int length=count(head);
    struct ListNode* dummyHead = (struct ListNode*) calloc (1, sizeof(struct ListNode));
    dummyHead->next=head;
    struct ListNode* p=dummyHead; 
    n=length-n;

    while(n>0)
    {      
        n--;
        p=p->next;
    }
    p->next=p->next->next;

    return dummyHead->next; 
}

在這裏插入圖片描述

雙指針一次遍歷解法

/**
 * Definition for singly-linked list.
 struct ListNode {
      int val;
      struct ListNode *next;
 };*/
 

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){

    struct ListNode* dummyhead= (struct ListNode*) calloc (1, sizeof(struct ListNode));;
    dummyhead->next=head;
    struct ListNode* first=dummyhead;
    struct ListNode* second=dummyhead;
    
    for(int i=1;i<=n+1;i++)
    {
        first=first->next;
    }
    while(first)
    {
        first=first->next;
        second=second->next;
    }
    second->next=second->next->next;
    return dummyhead->next;
}

在這裏插入圖片描述
回顧

  1. 修改後的代碼中,在count函數使用do…while循環時,執行耗時0ms,使用while循環時執行耗時4ms(存疑)
  2. 利用虛擬頭指針,尤其是對於刪除第一個節點時大有進益
  3. 雙指針NB

4 找樹左下角的值

2020-4-30

給定一個二叉樹,在樹的最後一行找到最左邊的值。示例 :

輸入:

    1
   / \
  2   3
 /   / \
4   5   6
   /
  7

輸出:
7

leetcode AC版本:

將層次遍歷改爲先右子樹後左子樹,則最後一個出隊的值即爲所求

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     left;
 *     struct TreeNode *right;
 * };
 */

#define MaxSize 10000
typedef struct queue {
    struct TreeNode* data[MaxSize];
    int front;
    int rear;
}Queue;

Queue Q;

void initilize() { 
    Q.front = 0;
    Q.rear = 0;
}

void Push(struct TreeNode* root) { 
    Q.data[++Q.rear] = root;
}

struct TreeNode* Pop() { 
    return Q.data[++Q.front];
}

int empty() { 
    return Q.rear == Q.front;
}

int findBottomLeftValue(struct TreeNode* root)
{
    struct TreeNode *temp= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
    if (root == NULL) return 0;
    Push(root);
    while (!empty()) {
        temp = Pop();
        if (temp->right)     
            Push(temp->right);
        if (temp->left)    
            Push(temp->left);
    }
    return temp->val;
}

在這裏插入圖片描述

5 三數之和

2020-5-1

給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。注意:答案中不可以包含重複的三元組。

示例:
給定數組 nums = [-1, 0, 1, 2, -1, -4],滿足要求的三元組集合爲:[[-1, 0, 1],[-1, -1, 2]]

在這裏插入圖片描述

作者:wu_yan_zu
鏈接:https://leetcode-cn.com/problems/3sum/solution/pai-xu-shuang-zhi-zhen-zhu-xing-jie-shi-python3-by/

leetcode AC版本:

/**
 * 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().
 */

void swap(int arr[], int low, int high)
{
    int temp;
    temp = arr[low];
    arr[low] = arr[high];
    arr[high] = temp;
}

int Partition(int array[], int low, int high) {
    int base = array[low];
    while (low < high) {
        while (low < high && array[high] >= base) {
            high--;
        }
        swap(array, low, high);//array[low] = array[high];
        while (low < high && array[low] <= base) {
            low++;
        }
        swap(array, low, high);//array[high] = array[low];
    }
    array[low] = base;
    return low;
}
void QuickSort(int array[], int low, int high) {
    if (low < high) {
        int base = Partition(array, low, high);
        QuickSort(array, low, base - 1);
        QuickSort(array, base + 1, high);
    }
}

int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes)
{
    *returnSize = 0;   // 參數returnSize用來作爲二維數組行數下標的指針
    if (numsSize < 3)
    {
        return NULL;
    }
    QuickSort(nums, 0, numsSize - 1);
    int L = 0, R = 0;
    int** returnArray = (int**)malloc(sizeof(int*) * numsSize * numsSize);
    *returnColumnSizes = (int*)malloc(sizeof(int) * (numsSize) * (numsSize));

    for (int i = 0; i < numsSize-2; i++)
    {
        if (nums[i] > 0)
        {
            return returnArray;
        }

        L = i + 1;
        R = numsSize - 1;

        while (L < R)
        {
            if (nums[i] + nums[L] + nums[R] == 0)
            {
                returnArray[*returnSize] = (int*)malloc(sizeof(int) * 3); // 每次找到一組,二級指針就分配三個空,用的時候申請,可以節省空間
                (*returnColumnSizes)[*returnSize] = 3;
                returnArray[*returnSize][0] = nums[i];
                returnArray[*returnSize][1] = nums[L];
                returnArray[*returnSize][2] = 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 (nums[i] + nums[L] + nums[R] < 0)
            {
                L++;
            }
            else //if(nums[i]+nums[L]+nums[R]>0)
            {
                R--;
            }
        }
        while ((nums[i]==nums[i+1])&&(i<numsSize-2))
        {
            i++;
        }
    }
    return returnArray;
}

在這裏插入圖片描述
庫函數qsort

https://www.cnblogs.com/xinlovedai/p/6233383.html

功能: 快速排序
頭文件:stdlib.h
用法: void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
參數:
1 待排序數組首元素的地址
2 數組中待排序元素數量
3 各元素的佔用空間大小
4 指向函數的指針,用於確定排序的順序
注意:數組中可以存儲數字,字符,或者結構體都行。

使用庫函數進行排序的版本

int compInc(const void *a, const void *b)   //遞增
{
    return *(int *)a - *(int *)b;
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes)
{
    *returnSize = 0;   // 參數returnSize用來作爲二維數組行數下標的指針
    if (numsSize < 3)
    {
        return NULL;
    }
    qsort(nums, numsSize, sizeof(int), compInc);
    int L = 0, R = 0;
    int maxSize=numsSize*6;   //試出來的6
    int** returnArray = (int**)malloc(sizeof(int*) * maxSize);
    *returnColumnSizes = (int*)malloc(sizeof(int) * maxSize);

    for (int i = 0; i < numsSize-2; i++)
    {
        if (nums[i] > 0)
        {
            return returnArray;
        }

        L = i + 1;
        R = numsSize - 1;

        while (L < R)
        {
            if (nums[i] + nums[L] + nums[R] == 0)
            {
                returnArray[*returnSize] = (int*)malloc(sizeof(int) * 3); 
                (*returnColumnSizes)[*returnSize] = 3;
                returnArray[*returnSize][0] = nums[i];
                returnArray[*returnSize][1] = nums[L];
                returnArray[*returnSize][2] = 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 (nums[i] + nums[L] + nums[R] < 0)
            {
                L++;
            }
            else 
            {
                R--;
            }
        }
        while ((nums[i]==nums[i+1])&&(i<numsSize-2))
        {
            i++;
        }
    }
    return returnArray;
}

在這裏插入圖片描述

複雜度分析

排序:O(nlogn)O(nlogn)
ii的遍歷:O(n)O(n),L和R的雙遍歷:O(n)O(n)
O(nlogn)+O(n)O(n)=O(n2)O(nlogn)+O(n)*O(n)=O(n^2)

回顧

  1. 考慮到一些特殊情況,寫在所有操作前面,如本題中數組長度小於3時直接返回NULL
  2. 去重是本題的關鍵點,主要利用排序後的數組相同的數字的相鄰的特性
  3. int* returnSize,用來作爲二維數組行數下標的指針
  4. int** returnColumnSizes,用來返回列數,需要malloc,在有和爲零的情況下賦值爲3
  5. 二維數組的列數一般用二級指針比較方便

6 兩數相除

2020-5-2

給定兩個整數,被除數 dividend 和除數 divisor。將兩數相除,要求不使用乘法、除法和 mod 運算符。返回被除數 dividend 除以除數 divisor 得到的商。整數除法的結果應當截去(truncate)其小數部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2

示例 1:
輸入: dividend = 10, divisor = 3
輸出: 3
解釋: 10/3 = truncate(3.33333…) = truncate(3) = 3

示例 2:
輸入: dividend = 7, divisor = -3
輸出: -2
解釋: 7/-3 = truncate(-2.33333…) = -2

提示:

  • 被除數和除數均爲 32 位有符號整數。
  • 除數不爲 0。
  • 假設我們的環境只能存儲 32 位有符號整數,其數值範圍是[231,2311][−2^{31}, 2^{31} − 1]。本題中,如果除法結果溢出,則返回 23112^{31} − 1

1 一種使除數加倍增加的遞歸算法

  • 利用多次的減法可以達到除法的效果,但是一個一個減,運行結果會超時
  • 越界問題需要特別注意,出現負數時需要取負,TminT_{min}取負越界,則用long定義
  • TminT_{min}:INT_MIN
  • TmaxT_{max}:INT_MAX
long div1111(long a, long b)
{ 
    if(a<b) return 0;
    long count = 1;
    long tb = b; // 在後面的代碼中不更新b
    while((tb+tb)<=a)
    {
        count = count + count; // 最小解翻倍
        tb = tb+tb; // 當前測試的值也翻倍
    }
    return count + div1111(a-tb,b);
}

int divide(int dividend, int divisor)
{
    long D=dividend;
    long d=divisor;
    int sign=1;
    if(D == 0) return 0;
    if(d == 1) return dividend;
    if(d == -1)
    {
        if(D>INT_MIN) return -D;// 只要不是最小的那個整數,都是直接返回相反數就好啦
        return INT_MAX;// 是最小的那個,返回最大的整數
    }
 
    if((D>0&&d<0) || (D<0&&d>0))
    {
        sign = -1;
    }
    if(D<0)D=-D;
    if(d<0)d=-d;
    
    long res = div1111(D,d);
    if(sign>0)return res>INT_MAX?INT_MAX:res;
    return -res;
}

2 使用移位

左移爲除2,從2312^{31}開始對指數ii遞減,找到能使“被除數-除數2i*2^{i}”小於除數的iians++=2ians++=2^{i}
右移爲乘2,被除數=被除數-除數2i*2^{i},循環

特殊情況:

  • 被除數爲0,返回0
  • 被除數是1,返回除數
  • 被除數是-1,除數爲TminT_{min},返回TmaxT_{max}
  • 被除數是-1,除數大於TminT_{min},返回-除數
  • 被除數是TminT_{min},除數爲TminT_{min},返回1
  • 被除數不爲TminT_{min},除數爲TminT_{min},返回0
int divide(int dividend, int divisor)
{
    int sign=1,res=0;
    if((dividend>0&&divisor<0) || (dividend<0&&divisor>0))sign = -1;

    if(dividend == 0) return 0;
    if(divisor == 1) return dividend;
    if(divisor == -1)
    {
        if(dividend>INT_MIN) return -dividend;
        return INT_MAX;
    }

    if(dividend == INT_MIN && divisor == INT_MIN)return 1;
    else if(divisor == INT_MIN)return 0;

    if(dividend == INT_MIN)         // 若被除數爲INT_MIN,先減一次,在再進行運算
    {
        dividend += abs(divisor);
        res++;
    }

    int D=abs(dividend);
    int d=abs(divisor);

    for(int i = 31; i >= 0; i--)
    {
        if((D >> i) >= d)
        {
            res += 1 << i;
            D -= d << i;
        }
    }

    if(sign>0)return res>INT_MAX?INT_MAX:res;
    return -res;
}

在這裏插入圖片描述

回顧

  • 如果是32位的機器,那麼long和int都是4個字節,可以使用int64_t進行替代,這樣代碼的可移植性會更好
  • (dividend ^ divisor) < 0;//用異或來計算是否符號相異
  • 邊界坑多
  • 在定義div函數時報錯error: conflicting types for ‘div’ 1 ,大概是函數名重複

7 合併兩個有序列表

2020-5-2

將兩個升序鏈表合併爲一個新的升序鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。

示例:
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4

1 暴力迭代

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode *temp=(struct ListNode*) calloc(1, sizeof(struct ListNode));
    struct ListNode *L=temp;
    struct ListNode *a=l1;
    struct ListNode *b=l2;
    if(l1==NULL)return l2;
    if(l2==NULL)return l1;
    while(a&&b)
    {
        if(a->val<b->val)
        {
            temp->next=a;
            a=a->next;
            temp=temp->next;
        }
        else
        {
            temp->next=b;
            b=b->next;
            temp=temp->next;
        }
    }
    if(a)temp->next=a;
    if(b)temp->next=b;
    return L->next;
}

在這裏插入圖片描述

  • 時間複雜度:O(n+m)O(n + m) ,其中nnmm 分別爲兩個鏈表的長度。因爲每次循環迭代中,l1 和 l2 只有一個元素會被放進合併鏈表中, 因此 while 循環的次數不會超過兩個鏈表的長度之和。所有其他操作的時間複雜度都是常數級別的,因此總的時間複雜度爲 O(n+m)O(n+m)

  • 空間複雜度:O(1)O(1) 。我們只需要常數的空間存放若干變量。

2 遞歸

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{  
    if(l1==NULL)return l2;
    if(l2==NULL)return l1;
    if(l1->val < l2->val) 
    {
        l1->next = mergeTwoLists(l1->next, l2);
        return l1;
    } 
    else 
    {
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
}

在這裏插入圖片描述

  • 時間複雜度:O(n+m)O(n + m),其中 n和 m 分別爲兩個鏈表的長度。因爲每次調用遞歸都會去掉 l1 或者 l2 的頭節點(直到至少有一個鏈表爲空),函數 mergeTwoList 至多隻會遞歸調用每個節點一次。因此,時間複雜度取決於合併後的鏈表長度,即 O(n+m)O(n+m)

  • 空間複雜度:O(n+m)O(n + m),其中 n 和 m 分別爲兩個鏈表的長度。遞歸調用 mergeTwoLists 函數時需要消耗棧空間,棧空間的大小取決於遞歸調用的深度。結束遞歸調用時 mergeTwoLists 函數最多調用 n+mn+m 次,因此空間複雜度爲 O(n+m)O(n+m)

8 無重複字符的最長子串

2020-5-3

給定一個字符串,請你找出其中不含有重複字符的最長子串的長度。

示例 1:
輸入: “abcabcbb”
輸出: 3
解釋: 因爲無重複字符的最長子串是 “abc”,所以其長度爲 3。

示例 2:
輸入: “bbbbb”
輸出: 1
解釋: 因爲無重複字符的最長子串是 “b”,所以其長度爲 1。
示例 3:

輸入: “pwwkew”
輸出: 3
解釋: 因爲無重複字符的最長子串是 “wke”,所以其長度爲 3。

滑動窗口 無hash版本

int lengthOfLongestSubstring(char * s){
    int n=strlen(s);
    int max=0,l=0,h=0,length;
    while(l<=h&&h<n)
    {
        for(int i=l;i<=h;i++)
        {
            if(*(s+i)==*(s+h+1))
            {
                length=h-l+1;
                l=i+1;  //將起始點剛好不包括重複的字符
                if(length>max)max=length;
            }
        }
        h++;
        if(h==n)
        {
            length=h-l;
            if(length>max)max=length;
        }   
    }
    return max;
}

在這裏插入圖片描述
回顧

  • 哈希集合
  • 子串->滑動窗口
  • 時間複雜度 存疑

9 二叉樹的中序遍歷

2020-5-4

1 遞歸

  • returnSize是返回向量的角標
  • 返回向量需要用calloc分配
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
//Note: The returned array must be malloced, assume caller calls free().

#define MAXSIZE 1000

void inorder (struct TreeNode* root, int *returnSize, int *returnArray){
    if (root->left == NULL && root->right == NULL)
    {
        returnArray[*returnSize] = root->val;
        (*returnSize)++;
        return;
    }
    if (root->left != NULL){
        inorder(root->left, returnSize, returnArray);
    }
    returnArray[*returnSize] = root->val;
    (*returnSize)++;
    if (root->right != NULL)
    {
        inorder(root->right, returnSize, returnArray);
    }
}

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    *returnSize = 0;
    int *returnArray = (int*)calloc(MAXSIZE, sizeof(int));
    if (root == NULL){return NULL;}
    inorder(root, returnSize, returnArray);
    return returnArray;
}

在這裏插入圖片描述

2用棧迭代

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
//Note: The returned array must be malloced, assume caller calls free().
#define MaxSize 1000
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.top+1;
    S.data[S.top] = root;
}

struct TreeNode* pop() {
    S.top=S.top-1; 
    return S.data[S.top+1];
}

int empty() { 
    return S.top==0;
}

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    *returnSize = 0;
    struct TreeNode* current=root;
    int *returnArray = (int*)calloc(MaxSize,sizeof(int));
    if (root == NULL){return NULL;}

    while(current!=NULL||!empty())
    {
       
        if(current!=NULL)
        {
            push(current);
            current=current->left;
        }
        else
        {
            current=pop();
            returnArray[*returnSize] = current->val;
            (*returnSize)++;
            current=current->right;
        }
    }  
    return returnArray;
}

在這裏插入圖片描述

10 最大子序和

2020-5-4

給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。

1 究極暴力版本

#define MAXSUM -2147483647
int maxSubArray(int* nums, int numsSize){
    int low,high,sum;
    int maxSum=MAXSUM;
    for(int i=0;i<numsSize;i++)
    {
        sum=0;
        for(int j=i;j<numsSize;j++)
        {
            sum=sum+nums[j];
            if(sum>maxSum)
            {
                maxSum=sum;
                low=i;
                high=j;
            }
        }
    }
    return maxSum;
}

在這裏插入圖片描述

2 貪心算法

若已知A[1,...,j]A[1,...,j]的最大子數組的情況下,則A[1,...,j+1]A[1,...,j+1]的最大子數組,要麼是A[1,...,j]A[1,...,j]的最大子數組,要麼是某個子數組A[i,...,j+1](1ij+1)A[i,...,j+1](1\leqslant i \leqslant j+1),在已知A[1,...,j]A[1,...,j]的最大子數組的情況下,可以在線性時間內找出形如A[i,...,j+1]A[i,...,j+1]的最大子數組

若當前指針所指元素之前的和小於零,則丟棄當前元素之前的數列

#define MAXSUM -2147483647
int maxSubArray(int* nums, int numsSize){
    int low,high,sum=MAXSUM,maxSum=MAXSUM,currentHigh,currentLow;
    for(int i=0;i<numsSize;i++)
    {
        currentHigh=i;
        if(sum>0)sum=sum+nums[i];
        else{
            currentLow=i;
            sum=nums[i];
        }
        if(sum>maxSum){
            maxSum=sum;
            low=currentLow;
            high=currentHigh;
        }
    }
    return maxSum;
}

在這裏插入圖片描述

3 動態規劃

假設 nums 數組的長度是 nn,下標從 00n1n−1
我們用 aia_i代表 nums[i],用 f(i)f(i)代表以第 i個數結尾的「連續子數組的最大和」,那麼很顯然我們要求的答案就是:

max0in1{f(i)}\max_{0 \leq i \leq n - 1} \{ f(i) \}

因此我們只需要求出每個位置的 f(i)f(i),然後返回ff數組中的最大值即可。
我們可以考慮 aia_i單獨成爲一段還是加入 f(i1)f(i - 1) 對應的那一段,這取決於 aia_if(i1)+aif(i - 1) + a_i的大小,我們希望獲得一個比較大的,於是可以寫出這樣的動態規劃轉移方程:

f(i)=max{f(i1)+ai,ai}f(i) = \max \{ f(i - 1) + a_i, a_i \}
若前一個元素大於零,則將其加到當前元素上

int max(int a,int b)
{
    if(a>b)return a;
    else return b;
}

int maxSubArray(int* nums, int numsSize){
    int pre = 0, maxAns = nums[0];
        for (int i=0;i<numsSize;i++) {
            pre = max(pre + nums[i], nums[i]);
            maxAns = max(maxAns, pre);
        }
        return maxAns;
}

在這裏插入圖片描述

4 分治

int maxCrossingSubarray(int* nums,int low,int mid,int high)
{
    int left_sum=-32768;
    int sum=0,max_left;
    for(int i=mid;i>=low;i--)
    {
        sum=sum+nums[i];
        if(sum>left_sum)
        {
            left_sum=sum;
            max_left=i;
        }
    }
    int right_sum=-32768;
    int max_right;
    sum=0;
    for(int j=mid+1;j<=high;j++)
    {
        sum=sum+nums[j];
        if(sum>right_sum)
        {
            right_sum=sum;
            max_right=j;
        }
    }
    return left_sum+right_sum;
}

int max(int* nums,int low,int high)
{
    int left_sum,right_sum,cross_sum,mid;
    if(low==high)return nums[low];
    else
    {
        mid=(low+high)/2;
        left_sum=max(nums,low,mid);
        right_sum=max(nums,mid+1,high);
        cross_sum=maxCrossingSubarray(nums,low,mid,high);
        if(left_sum>=right_sum&&left_sum>=cross_sum)return left_sum;
        else if(right_sum>=left_sum&&right_sum>=cross_sum)return right_sum;
        else return cross_sum;
    }
}

int maxSubArray(int* nums, int numsSize){
    return max(nums,0,numsSize-1);
}

在這裏插入圖片描述

11 跳躍遊戲 II

2020-5-4

給定一個非負整數數組,你最初位於數組的第一個位置。數組中的每個元素代表你在該位置可以跳躍的最大長度。你的目標是使用最少的跳躍次數到達數組的最後一個位置。

示例:
輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最後一個位置的最小跳躍數是 2。從下標爲 0 跳到下標爲 1 的位置,跳 1 步,然後跳 3 步到達數組的最後一個位置。

說明:假設你總是可以到達數組的最後一個位置。

貪心

選擇可達範圍內的點下一步可達最遠的位置

int jump(int* nums, int numsSize){
    int count=0,i=0;
    while(i<numsSize-1)
    {    
        count++;
        int max=0,Max=0;
        if(nums[i]+i>=numsSize-1)return count;
        for(int j=0;j<=nums[i]&&i+j<numsSize;j++)
        {
            if(j+nums[i+j]>=Max)
            {
                max=j;
                Max=j+nums[i+j];   
            }
        }
        i=i+max;
    }
    return count;
}

在這裏插入圖片描述

12 驗證二叉搜索樹

2020-5-5

給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。
假設一個二叉搜索樹具有如下特徵:
節點的左子樹只包含小於當前節點的數。
節點的右子樹只包含大於當前節點的數。
所有左子樹和右子樹自身必須也是二叉搜索樹。

1 遞歸

設置上界與下界,左子樹小於上界,右子樹大於下界

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool helper(struct TreeNode*root,long lower,long upper){
    if(root==NULL)return true;
    if(root->val<=lower||root->val>=upper)return false;
    return helper(root->left,lower,root->val)&&helper(root->right,root->val,upper);
}

bool isValidBST(struct TreeNode* root){
    return helper(root,LONG_MIN,LONG_MAX);
}

在這裏插入圖片描述

2 中序遍歷

二叉搜索樹中序遍歷的結果是一個遞增數列,則在遍歷過程中儲存上一個出棧的值,若當前出棧的值小於等於上一個出棧的值,則返回false

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
//Note: The returned array must be malloced, assume caller calls free().
#define MaxSize 1000
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.top+1;
    S.data[S.top] = root;
}

struct TreeNode* pop() {
    S.top=S.top-1; 
    return S.data[S.top+1];
}

int empty() { 
    return S.top==0;
}

bool isValidBST(struct TreeNode* root){
    long K=LONG_MIN;
    struct TreeNode* current=root;
    if (root == NULL){return true;}

    while(current!=NULL||!empty())
    {
        if(current!=NULL)
        {
            push(current);
            current=current->left;
        }
        else
        {
            current=pop();
            if(current->val<=K)return false;
            K=current->val;
            current=current->right;
        }
    }  
    return true;
}

特殊測試用例

[2147483647]
[]

13 最低票價

2020-5-6

在一個火車旅行很受歡迎的國度,你提前一年計劃了一些火車旅行。在接下來的一年裏,你要旅行的日子將以一個名爲 days 的數組給出。每一項是一個從 1 到 365 的整數。

火車票有三種不同的銷售方式:

一張爲期一天的通行證售價爲 costs[0] 美元;
一張爲期七天的通行證售價爲 costs[1] 美元;
一張爲期三十天的通行證售價爲 costs[2] 美元。
通行證允許數天無限制的旅行。 例如,如果我們在第 2 天獲得一張爲期 7 天的通行證,那麼我們可以連着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在給定的列表 days 中列出的每一天的旅行所需要的最低消費。

示例 1:
輸入:days = [1,4,6,7,8,20], costs = [2,7,15]
輸出:11
解釋:
例如,這裏有一種購買通行證的方法,可以讓你完成你的旅行計劃:
在第 1 天,你花了 costs[0] = $2 買了一張爲期 1 天的通行證,它將在第 1 天生效。在第 3 天,你花了 costs[1] = $7 買了一張爲期 7 天的通行證,它將在第 3, 4, …, 9 天生效。在第 20 天,你花了 costs[0] = $2 買了一張爲期 1 天的通行證,它將在第 20 天生效。你總共花了 $11,並完成了你計劃的每一天旅行。

示例 2:
輸入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
輸出:17
解釋:
例如,這裏有一種購買通行證的方法,可以讓你完成你的旅行計劃:
在第 1 天,你花了 costs[2] = $15 買了一張爲期 30 天的通行證,它將在第 1, 2, …, 30 天生效。在第 31 天,你花了 costs[0] = $2 買了一張爲期 1 天的通行證,它將在第 31 天生效。 你總共花了 $17,並完成了你計劃的每一天旅行。

提示:

1 <= days.length <= 365
1 <= days[i] <= 365
days 按順序嚴格遞增
costs.length == 3
1 <= costs[i] <= 1000

動態規劃,遍歷365天

ii天沒有出行計劃,即days數組中不含ii
dp(i)=dp(i+1)dp(i)=dp(i+1)
否則
dp(i)=min{cost(j)+dp(i+j)},j1,7,30dp(i)=min\{ cost(j)+dp(i+j)\},j∈{1,7,30}

非遞歸版本

int Min(int a,int b,int c){
    int min=a;
    if(b<min)min=b;
    if(c<min)min=c;
    return min;
}

int Contain(int *A,int length,int a)
{
    for(int i=0;i<length;i++)
    {
        if(a==A[i])
        return 1;
    }
    return 0;
}

int mincostTickets(int* days, int daysSize, int* costs, int costsSize){
    int dp_cost[400]={0};   //起碼要有365+30 不然會溢出
    for(int k=365;k>0;k--)
    {
        int J=Contain(days,daysSize,k);
        if(J)
        {
            dp_cost[k]=Min(dp_cost[k+1]+costs[0],dp_cost[k+7]+costs[1],dp_cost[k+30]+costs[2]);
        }
        else
        dp_cost[k]=dp_cost[k+1];
    }
    return dp_cost[1];
}

在這裏插入圖片描述
上述代碼中使用一個Contain函數判斷數組中是否存在k,構成兩層嵌套,時間複雜度高,改成遍歷過程中進行判斷:

int Min(int a,int b,int c){
    int min=a;
    if(b<min)min=b;
    if(c<min)min=c;
    return min;
}

int mincostTickets(int* days, int daysSize, int* costs, int costsSize){
    int dp_cost[400]={0};
    int i=daysSize-1;
    for(int k=365;k>0;k--)
    {
        
        if(i>=0&&days[i]==k)
        {
            dp_cost[k]=Min(dp_cost[k+1]+costs[0],dp_cost[k+7]+costs[1],dp_cost[k+30]+costs[2]);
            i--;
        }
        else
        dp_cost[k]=dp_cost[k+1];
    }
    return dp_cost[1];
}

在這裏插入圖片描述
再進一步,將自己寫的min函數替換爲math.h中的fmin函數,時間複雜度反而增加了qaq

int mincostTickets(int* days, int daysSize, int* costs, int costsSize){
    int dp_cost[400]={0};
    int i=daysSize-1;
    for(int k=365;k>0;k--)
    {
        
        if(i>=0&&days[i]==k)
        {
            dp_cost[k]=fmin(fmin(dp_cost[k+1]+costs[0],dp_cost[k+7]+costs[1]),dp_cost[k+30]+costs[2]);
            i--;
        }
        else
        dp_cost[k]=dp_cost[k+1];
    }
    return dp_cost[1];
}

在這裏插入圖片描述
跳過這些不需要出行的日期的算法

假設當前在第i個元素,則分別計算滿足 day[i]+1day[j];day[i]+7day[j];day[i]+30day[j]day[i]+1\leq day[j] ;day[i]+7\leq day[j];day[i]+30\leq day[j]的最小元素jj,比較三個中情況下的cost取最小的作爲下一步的狀態

似懂非懂,沒事兒多看看
https://leetcode-cn.com/problems/minimum-cost-for-tickets/solution/zui-di-piao-jie-by-leetcode-solution/

回顧

  • 根據轉移方程可得到如下的遞歸過程,這怎麼在C代碼中實現啊 存疑
int dp(int k)
{
    if(k>365)return 0;
    int J=Contain(days,daysSize,k);
    if(J)
    {
        dp_cost(k)=Min(dp(k+1)+costs[0],dp(k+7)+costs[1],dp(k+30)+costs[2]);
    }
    else
    dp(k)=dp(k+1);
}
  • 用min(0, i+1) min(0, i+7) min(0, i+30)來防止越界

14 另一個樹的子樹

2020-5-7

給定兩個非空二叉樹 s 和 t,檢驗 s 中是否包含和 t 具有相同結構和節點值的子樹。s 的一個子樹包括 s 的一個節點和這個節點的所有子孫。s 也可以看做它自身的一棵子樹。

暴力匹配

  1. 使用遞歸寫一個函數判斷兩樹是否相等
  2. 遍歷s中的每個節點,其對應的子樹一一與t比較
  3. 時間複雜度爲 O(s×t)O(∣s∣×∣t∣)

使用層序遍歷的非迭代算法,定義MaxSize 10000不夠用

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
#define MaxSize 100000
typedef struct queue {
    struct TreeNode* data[MaxSize];
    int front;
    int rear;
}Queue;

Queue Q;

void initilize() { 
    Q.front = 0;
    Q.rear = 0;
}

void Push(struct TreeNode* root) { 
    Q.data[++Q.rear] = root;
}

struct TreeNode* Pop() { 
    return Q.data[++Q.front];
}

int empty() { 
    return Q.rear == Q.front;
}

bool compare(struct TreeNode* a, struct TreeNode* b)
{
    if(a==NULL&&b==NULL)return true;
    else if(a==NULL||b==NULL)return false;
    else if(a->val!=b->val)return false;
    else{
        return compare(a->left,b->left)&&compare(a->right,b->right);
    }
}

bool isSubtree(struct TreeNode* s, struct TreeNode* t){
    if (s == NULL&&t!=NULL) return false;
    if (s == NULL&&t==NULL) return true;
    bool T=false;
    struct TreeNode *temp= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
    Push(s);
    while (!empty()) {
        temp = Pop();
        bool T=compare(temp,t);
        if(T) return T; 
        if (temp->left)     
            Push(temp->left);
        if (temp->right)    
            Push(temp->right);
    }
    return T;
}

在這裏插入圖片描述
一個更簡潔的比較函數與遞歸遍歷

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
    return (p == q) || (p != NULL && q != NULL && p->val == q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right));
}

bool isSubtree(struct TreeNode* s, struct TreeNode* t){
    if (s == NULL&&t!=NULL) return false;
    if (s == NULL&&t==NULL) return true;
    else if (s->val == t->val && isSameTree(s, t)) return true;
    else return isSubtree(s->left, t) || isSubtree(s->right, t);
}

在這裏插入圖片描述
沒有使用隊列,內存使用少了一些

回顧

  • 下限很低,上限很高,IG式題目
  • 加空節點後遍歷樹,做字符串匹配,使用KMP匹配,時間複雜度爲O(s+t)O(∣s∣+∣t∣)
  • 官方題解中樹哈希的算法,打擾了

15 最大正方形

2020-5-8

在一個由 0 和 1 組成的二維矩陣內,找到只包含 1 的最大正方形,並返回其面積。

示例:
輸入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
輸出: 4

1 暴力窮舉

分別檢查數組中每一個元素,在元素爲1的基礎上判斷加上一行一列後元素的值是否均爲1
時間複雜度:O(mnmin(m,n)2)O(mn \min(m,n)^2)

int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
    int maxlength=0,flag,maxSide=0;
    for(int i=0;i<matrixSize;i++)
    {
        for(int j=0;j<matrixColSize[i];j++)
        {
            maxSide=fmin(matrixSize-i,matrixColSize[i]-j);
            if(matrix[i][j]=='1')
            {
                maxlength=fmax(maxlength,1);
                flag=1;
                for(int k=1;k<maxSide;k++)
                {
                    if(matrix[i+k][j+k]=='0')break;
                    for(int s=0;s<k;s++)
                    {
                        if(matrix[i+s][j+k]=='0'||matrix[i+k][j+s]=='0')
                        {
                            flag=0;
                            break;
                        }
                    } 
                    if(flag==1)maxlength=fmax(maxlength,k+1);
                    else break;
                } 
            }
        }
    }
    return maxlength*maxlength;
}

2 動態規劃

dp(i,j)dp(i, j) 表示以 (i,j)(i,j) 爲右下角,且只包含 1 的正方形的邊長最大值

  • 如果該位置的值是 0,則 dp(i,j)=0dp(i, j) = 0
  • 如果該位置的值是 1,則 dp(i,j)dp(i, j)的值由其上方、左方和左上方的三個相鄰位置的 dp 值決定。具體而言,當前位置的元素值等於三個相鄰位置的元素中的最小值加 1,狀態轉移方程如下:

dp(i,j)=min(dp(i1,j),dp(i1,j1),dp(i,j1))+1dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1

  • 時間複雜度:O(mn)O(mn)
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
    int maxlength=0;	
    int **dp= (int**)malloc(sizeof(int*)*matrixSize);
    for(int i = 0; i < matrixSize; i++){
	    dp[i] = (int*)malloc(sizeof(int)* matrixColSize[0]);
	}

    for(int i=0;i<matrixSize;i++)
    {
        for(int j=0;j<matrixColSize[i];j++)
        {
            if(matrix[i][j]=='0')dp[i][j]=0;
            else if(matrix[i][j]=='1'&&(i==0||j==0))dp[i][j]=1;        
            else{
                dp[i][j]=fmin(fmin(dp[(int)fmax(i-1,0)][j],dp[(int)fmax(i-1,0)][(int)fmax(j-1,0)]),dp[i][(int)fmax(j-1,0)])+1;
            }
            if(dp[i][j]>maxlength)maxlength=dp[i][j];
        }
    }
    return maxlength*maxlength;
}

使用一維數組

妙啊

int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){ 
    int maxlength=0,cross=0;	
    if(matrixSize==0)return 0;
	int *dp= (int*)malloc(sizeof(int)* matrixColSize[0]);
    for(int i=0;i<matrixSize;i++)
    {
        for(int j=0;j<matrixColSize[0];j++)
        {
            int temp = dp[j];
            if(matrix[i][j]=='0')dp[j]=0;
            else if(matrix[i][j]=='1'&&(i==0||j==0))dp[j]=1;        
            else{
                dp[j]=fmin(fmin(dp[j],dp[(int)fmax(j-1,0)]),cross)+1;
            }
            printf("%d ",dp[j]);
            if(dp[j]>maxlength)maxlength=dp[j];
            cross=temp;
        }
    }
    return maxlength*maxlength;
}

回顧

函數的變量中matrixSize爲行數,matrixCloSize是一維指針,長度爲matrixSize,每個元素的值都爲列數,即matrixColSize[0]=matrixColSize[1]=...=matrixColSize[matrixSize]matrixColSize[0]=matrixColSize[1]=...=matrixColSize[matrixSize]

以下爲用二級指針動態申請二維數組 :

int **dp= (int**)malloc(sizeof(int*)*matrixSize);
    for(int i = 0; i < matrixSize; i++){
	    dp[i] = (int*)malloc(sizeof(int)* matrixColSize[0]);
	}

16 x的平方根

2020-5-9

實現 int sqrt(int x) 函數。
計算並返回 x 的平方根,其中 x 是非負整數。
由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。

1 暴力窮舉

231=21474836482^{31}=2147483648

2147483647=46340.95\sqrt{2147483647}=46340.95

46341×46341=214748828146341\times46341=2147488281

46340×46340=314739560046340\times46340=3147395600

int mySqrt(int x){
    if(x>=2147395600&&x<=2147483647)return 46340;
    for(int i=1;i<=x;i++)
    {
        if(i*i==x)return i;
        else if(i*i>x)return i-1;
    }
    return 0;
}

2 二分查找

時間複雜度O(lgn)O(lgn)

int mySqrt(int x){
    int l=0,r=x,mid,ans=0;
    while(l<=r)
    {
        mid=(l+r)/2;
        if((long long)mid*mid<=x)
        {
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}

3 牛頓迭代

rrf(x)=0f(x)=0的根,選取x0x_0作爲rr的近似初始值,過點(x0,f(x0))(x_0,f(x_0))做曲線y=f(x)y=f(x)的切線L:y=f(x0)+f(x0)(xx0)L:y=f(x_0)+f'(x_0)(x-x_0),則LLxx軸交點的橫座標x1=x0f(x0)f(x0)x_1=x_0-\frac{f(x_0)}{f'(x_0)}rr的一次近似值

迭代公式xn+1=xnf(xn)f(xn)x_{n+1}=x_n-\frac{f(x_n)}{f'(x_n)}

  • 用浮點數得到結果,直接取整
  • 一般來說,可以判斷相鄰兩次迭代的結果的差值是否小於一個極小的非負數 ϵ\epsilon,其中 ϵ\epsilon 一般可以取 10610^{−6}10710^{-7}
  • 時間複雜度O(lgn)O(lgn)
int mySqrt(int x){
    double x1=x,x2,error=1;
    if(x==0)return 0;      //零不能做除數
    while(error>1e-7)
    {
        x2=0.5*x1+x/(2*x1);
        error=fabs(x2-x1);
        x1=x2;
    }
    return (int)x1;
}

17 二叉樹的最近公共祖先

2020-5-10

給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。
百度百科中最近公共祖先的定義爲:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”
例如,給定如下二叉樹: root = [3,5,1,6,2,0,8,null,null,7,4]
在這裏插入圖片描述
示例 1:
輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
輸出: 3
解釋: 節點 5 和節點 1 的最近公共祖先是節點 3。

示例 2:
輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
輸出: 5
解釋: 節點 5 和節點 4 的最近公共祖先是節點 5。因爲根據定義最近公共祖先節點可以爲節點本身。

說明:
所有節點的值都是唯一的。
p、q 爲不同節點且均存在於給定的二叉樹中。

超時!超時!超時!
寫Contain函數判斷節點是否在樹中,層序遍歷樹,返回最後一個滿足條件的節點

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
#define MaxSize 50000
typedef struct queue {
    struct TreeNode* data[MaxSize];
    int front;
    int rear;
}Queue;

Queue Q;

void initilize() { 
    Q.front = 0;
    Q.rear = 0;
}

void Push(struct TreeNode* root) { 
    Q.data[++Q.rear] = root;
}

struct TreeNode* Pop() { 
    return Q.data[++Q.front];
}

int empty() { 
    return Q.rear == Q.front;
}

int Contain(struct TreeNode* root, struct TreeNode* p){
    if(root==NULL)return 0;
    if(root->val==p->val)return 1;
    else return Contain(root->left,p)||Contain(root->right,p);
}

struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
    struct TreeNode* lowest= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
    struct TreeNode* temp= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
    Push(root);
    while (!empty()) {
        temp = Pop();
        if(Contain(temp,p)&&Contain(temp,q))lowest=temp;
        //printf("%d ",lowest->val);
        if (temp->left)     
            Push(temp->left);
        if (temp->right)    
            Push(temp->right);
    }
    return lowest;
} 

遞歸

  1. 樹爲空,返回NULLNULL
  2. 樹等於pp或等於qq,返回rootroot
  3. 定義節點leftleft表示當前節點的左子樹包含ppqqrightright同理
  4. 如果leftleftrightright同時不爲空,則當前節點的左右子樹分別包含ppqq,當前節點爲所求
  5. leftleft爲空,rightright不爲空,則所求在當前節點的右子樹,即返回節點rightright,可分爲兩種情況:
  • p,qp,q 其中一個在 rootroot的右子樹中,此時 rightright指向 pp(假設爲 pp );
  • p,qp,q兩節點都在rootroot的右子樹中,此時的 rightright指向最近公共祖先節點 ;
    若查詢的兩個節點爲15和20,在檢查節點1的時候,leftleft爲空,rightright不爲空

在這裏插入圖片描述
6. 後序遍歷,由下至上,最先滿足條件的就是最深的

struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
    if(root == NULL) return NULL;
    if(root == p || root == q) return root;

    struct TreeNode* left =  lowestCommonAncestor(root->left, p, q);
    struct TreeNode* right = lowestCommonAncestor(root->right, p, q);
       
    if(left == NULL)return right;
    if(right == NULL)return left;      
    if(left && right) return root;// p和q在兩側
    return NULL; // 必須有返回值
} 

18 pow(x, n)

2020-5-11

實現 pow(x, n) ,即計算 x 的 n 次冪函數。

示例 1:
輸入: 2.00000, 10
輸出: 1024.00000

示例 2:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25

說明:
-100.0 < x < 100.0
n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。

我的我的
我覺得思想和官方題解方法二類似,但是好像米有掌握精髓,超時 存疑

double myPow(double x, int n){
    if(n==0)return 1.0;
    if(x==1)return 1.0;
    double ans=x;
    int N=n;
    long i;
    if(n<0)N=-n;
    if(N==1)ans=x;
    if(N>1)
    {
        for(i=2;i<=N;i=i*2)
        {
            ans=ans*ans;
        }
        for(int k=1;k<=N-i/2;k++)
        {
            ans=ans*x;
        }
    }
    if(n>0)return ans;
    else return 1/ans;
}

分治 遞歸

210=25×252^{10}=2^5\times2^5

double positive(double x,int n){
    if(n==0)return 1;
    double ans=positive(x,n/2);
    if(n%2==1)return ans*ans*x;
    else return ans*ans;
}

double myPow(double x, int n){
    if(n==INT_MIN)return 1/(x*positive(x,INT_MAX));
    if(n==0)return 1;
    else if(n>0) return positive(x,n);
    else return 1.0/positive(x,-n);
}

時間複雜度:O(logn)O(logn)
空間複雜度:O(logn)O(logn)

迭代

在這裏插入圖片描述

double positive(double x,int N){
    double ans = 1.0;
    double x_contribute = x;
    while (N > 0) {
        if (N % 2 == 1) ans *= x_contribute;
        x_contribute *= x_contribute;
        N /= 2;
    }
    return ans;
}

double myPow(double x, int n){
    if(n==INT_MIN)return 1/(x*positive(x,INT_MAX));
    if(n==0)return 1;
    else if(n>0) return positive(x,n);
    else return 1.0/positive(x,-n);
}

回顧

  • 在越界的邊緣瘋狂試探 芝麻開門密碼:2147483648
  • 遞歸的返回值還需要再仔細琢磨
  • 能想象到二進制是真的牛批了

19 只出現一次的數

2020-5-14

給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素,線性時間複雜度不用額外空間

如果沒有時間複雜度和空間複雜度的限制,這道題有很多種解法,可能的解法有如下幾種:

  • 使用集合存儲數字。遍歷數組中的每個數字,如果集合中沒有該數字,則將該數字加入集合,如果集合中已經有該數字,則將該數字從集合中刪除,最後剩下的數字就是隻出現一次的數字。
  • 使用哈希表存儲每個數字和該數字出現的次數。遍歷數組即可得到每個數字出現的次數,並更新哈希表,最後遍歷哈希表,得到只出現一次的數字。
  • 使用集合存儲數組中出現的所有數字,並計算數組中的元素之和。由於集合保證元素無重複,因此計算集合中的所有元素之和的兩倍,即爲每個元素出現兩次的情況下的元素之和。由於數組中只有一個元素出現一次,其餘元素都出現兩次,因此用集合中的元素之和的兩倍減去數組中的元素之和,剩下的數就是數組中只出現一次的數字。
    上述三種解法都需要額外使用 O(n)O(n) 的空間

位運算

異或運算有以下三個性質:

  1. 任何數和0做異或運算,結果仍然是原來的數,即 a0=aa⊕0=a
  2. 任何數和其自身做異或運算,結果是 0,即 aa=0a⊕a=0
  3. 異或運算滿足交換律和結合律,即aba=baa=b(aa)=b0=ba⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b
int singleNumber(int* nums, int numsSize){
    int k = 0;
    for (int i=0;i<numsSize;i++) k^=nums[i];
    return k;
}

回顧

我琢磨了一個多小時,從和減去每一個數判斷奇偶性出發,各種分類討論,未果
計算機思維匱乏,怎麼能硬往數學想呢

20 和爲K的子數組

2020-5-15

給定一個整數數組和一個整數 k,你需要找到該數組中和爲 k 的連續的子數組的個數。

示例 1 :
輸入:nums = [1,1,1], k = 2
輸出: 2 , [1,1] 與 [1,1] 爲兩種不同的情況。

說明 :數組的長度爲 [1, 20,000],數組中元素的範圍是 [-1000, 1000] ,整數 k 的範圍是 [-1e7, 1e7]。

窮舉

有負數,不能大於k時跳出,有零,不能等於k時跳出

int subarraySum(int* nums, int numsSize, int k){
    int count=0;
    for(int i=0;i<numsSize;i++)
    {
        int sum=0;
        for(int s=i;s<numsSize;s++)
        {
            sum=sum+nums[s];
            if(sum==k)count++;
        }
    }
    return count;
}

時間複雜度:O(n2)O(n^2)
空間複雜度:O(1)O(1)

21 K 個一組翻轉鏈表

2020-5-16

給你一個鏈表,每 k 個節點一組進行翻轉,請你返回翻轉後的鏈表。k 是一個正整數,它的值小於或等於鏈表的長度。如果節點總數不是 k 的整數倍,那麼請將最後剩餘的節點保持原有順序。

示例:
給你這個鏈表:1->2->3->4->5
當 k = 2 時,應當返回: 2->1->4->3->5
當 k = 3 時,應當返回: 3->2->1->4->5

說明:
你的算法只能使用常數的額外空間。
你不能只是單純的改變節點內部的值,而是需要實際進行節點交換。

翻轉鏈表
設置兩個節點pre和next,pre初值設爲NULL,next節點指向當前指針的next節點,從表頭開始,逐次將當前節點的next指針指向pre,當前節點更新爲新的next,噹噹前節點爲空是退出,最後返回pre

struct ListNode* reverseGroup(struct ListNode* head){
    struct ListNode* pre=NULL;
    struct ListNode* curr=head;
    while(curr!=NULL)
    {
        struct ListNode* next=curr->next;
        curr->next=pre;
        pre=curr;
        curr=next;
    }
    return pre;
}

分組翻轉並連接

  1. 設置哨兵節點指向鏈表的頭節點
  2. 設置pre節點,指向當前組第一個節點,pre->next爲翻轉後的鏈表
  3. 設置end節點,是當前組的最後一個節點
  4. 設置start節點,爲當前組的頭結點
  5. 設置next節點,爲當前組最後一個節點next節點,爲組翻轉後next指針指向的節點
  6. pre初始值爲哨兵節點,每組的pre節點是上一組的start節點
  7. 循環k次得到每一組的end節點
  8. 當不足k個節點時,直接退出循環
struct ListNode* reverseKGroup(struct ListNode* head, int k){
    struct ListNode* dummy = (struct ListNode*)calloc(1, sizeof(struct ListNode));
    dummy->next=head;
    struct ListNode* pre = dummy;
    struct ListNode* end = dummy;
    while(end->next!=NULL)
    {
        for(int i=0;i<k&&end!=NULL;i++)end=end->next;
        if(end==NULL)break;
        struct ListNode* start=pre->next;
        struct ListNode* next=end->next;
        end->next=NULL;
        pre->next=reverseGroup(start);
        start->next=next;
        pre=start;
        end=pre; 
    }
    return dummy->next;
}

回顧

需要加一些輔助的節點,想清楚,容易亂
https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/

22 課程表2

2020-5-17

題目描述

現在你總共有 n 門課需要選,記爲 0 到 n-1。在選修某些課程之前需要一些先修課程。 例如,想要學習課程 0 ,你需要先完成課程 1 ,我們用一個匹配來表示他們: [0,1]給定課程總量以及它們的先決條件,返回你爲了學完所有課程所安排的學習順序。可能會有多個正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個空數組。

示例 :
輸入: 4, [[1,0],[2,0],[3,1],[3,2]]
輸出: [0,1,2,3] or [0,2,1,3]
解釋: 總共有 4 門課程。要學習課程 3,你應該先完成課程 1 和課程 2。並且課程 1 和課程 2 都應該排在課程 0 之後。因此,一個正確的課程順序是 [0,1,2,3] 。另一個正確的排序是 [0,2,1,3]

首先明確函數中各個參數的含義

int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize)
  • numCourses 課程總數
  • prerequisites 二級指針表示二維數組,即A=[[1,0],[2,0],[3,1],[3,2]];prerequisites[0][0]=1 prerequisites[1][0]=2
  • prerequisitesSize 二維數組的行數,即4
  • prerequisitesColSize 一維數組,表示A中每行對應的列數,即[2,2,2,2]
  • *returnSize返回數組的大小,初始設爲零,再逐步加一,不滿足條件時可直接置零輸出空數組

廣度優先搜索

  1. 統計各個節點的入度
  2. 將度爲零的節點入隊
  3. 出隊,並將以出隊元素爲隊首的節點的入度減一,若節點度爲零,入隊
  4. 若隊爲空之後仍有節點,返回空數組
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
#define MaxSize 10000
typedef struct queue{
    int data[MaxSize];
    int front;
    int rear;
}Queue;

Queue Q;
void initilize()
{
    Q.front=0;
    Q.rear=0;
}
void QueueIn(int d)
{
    Q.data[++Q.rear]=d;
}
int QueueOut()
{
    return Q.data[++Q.front];
}
int Empty()
{
    return Q.front==Q.rear;
}

int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize){
    //返回的數組及其大小  
    int *ans=(int*)calloc(numCourses+1,sizeof(int));
    *returnSize=0;
    //統計入度
    int *indegrees=(int*)calloc(numCourses+1,sizeof(int));
    for(int i=0;i<prerequisitesSize;i++)
    {
        int index=prerequisites[i][0];
        indegrees[index]++;
    }
    //度爲零 入隊
    for(int i=0;i<numCourses;i++)
    {
        if(indegrees[i]==0)QueueIn(i);
    }
    while(!Empty())
    {
        int temp=QueueOut();
        numCourses--;
        ans[*returnSize]=temp;
        (*returnSize)++;
        
        for(int i=0;i<prerequisitesSize;i++)
        {
            if(prerequisites[i][1]==temp)
            {
                indegrees[prerequisites[i][0]]--;
                if(indegrees[prerequisites[i][0]]==0)QueueIn(prerequisites[i][0]);
            }
        }
    }
    if(numCourses!=0)*returnSize=0;
    return ans;
}

深度優先搜索

  • 訪問到正在訪問狀態的節點,說明有環
  • 將訪問完成的節點一一入棧
  • 搜索過程中三種狀態:未被訪問 正在訪問 訪問完成
#define MaxSize 10000
typedef struct stack{
    int data[MaxSize];
    int top;
}Stack;
Stack S;

void initilize()
{
    S.top=0;
}
void push(int x)
{
    S.top++;
    S.data[S.top]=x;
}
int pop()
{
    S.top--;
    return S.data[S.top+1];
}
int Empty(){
    return S.top==0;
}

int visited[MaxSize];
int ring=0;
void DFS(int u,int m,int **prerequisites)
{
    visited[u]=1;
    for(int i=0;i<m;i++)  
    {
        if(prerequisites[i][1]==u)  //prerequisites[i][0]是鄰接點
        {
            if(visited[prerequisites[i][0]]==0)
            {
                DFS(prerequisites[i][0],m,prerequisites);
                if (ring==1)return;
            }
            else if(visited[prerequisites[i][0]]==1)  //有環
            {
                ring=1;
                return;
            } 
        }      
    }
    visited[u]=2;
    push(u);
}

int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize){
    int *ans=(int*)calloc(numCourses+1,sizeof(int));
    *returnSize=0;
    for(int i=0;i<numCourses;i++)
    {
        if(!visited[i])DFS(i,prerequisitesSize,prerequisites);
    }
    while(!Empty())
    {
        ans[*returnSize]=pop();
        (*returnSize)++;
    }
    if(ring==1)(*returnSize)=0;
    return ans;
}

同樣的輸入,測試結果是對的,提交運行就出錯,一腦袋問號 存疑
在這裏插入圖片描述

23 乘積最大子數組

2020-5-18

給你一個整數數組 nums ,請你找出數組中乘積最大的連續子數組(該子數組中至少包含一個數字),並返回該子數組所對應的乘積。

動態規劃

當之前的和爲整數,當前組的值爲整數時,需要保留負的結果,以便再次出現負值時負負得正

定義

dpMax[i]=max{dpMax[i1]nums[i],nums[i]}dpMax[i] = max\{dpMax[i-1] * nums[i],nums[i]\}
dpMin[i]=min{dpMin[i1]nums[i],nums[i]}dpMin[i] = min\{dpMin[i-1] * nums[i],nums[i]\}

分情況討論:

  • nums[i]>=0nums[i] >= 0 並且dpMax[i1]>0dpMax[i-1] > 0dpMax[i]=dpMax[i1]nums[i]dpMax[i] = dpMax[i-1] * nums[i]
  • nums[i]>=0nums[i] >= 0 並且dpMax[i1]<=0dpMax[i-1] < =0dpMax[i]=nums[i]dpMax[i] = nums[i]
  • nums[i]<0nums[i] < 0,並且dpMin[i1]<0dpMin[i-1] < 0dpMax[i]=dpMin[i1]nums[i]dpMax[i] = dpMin[i-1] * nums[i]
  • nums[i]<0nums[i] < 0,並且dpMin[i1]>=0dpMax[i]=nums[i]dpMin[i-1] >= 0,dpMax[i] = nums[i]

因此dpMax[i]=max(dpMax[i1]nums[i],dpMin[i1]nums[i],nums[i])dpMax[i] = max(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i])

int maxProduct(int* nums, int numsSize){
    int dpMax = nums[0];
    int dpMin = nums[0];
    int ans = nums[0];
    for (int i = 1; i < numsSize; i++) {
        int preMax = dpMax;
        dpMax = fmax(dpMin * nums[i], fmax(dpMax * nums[i], nums[i]));
        dpMin = fmin(dpMin * nums[i], fmin(preMax * nums[i], nums[i]));
        ans = fmax(ans, dpMax);
    }
    return ans;
}

當負數出現時則max與min進行交換再進行下一步計算

int max(int a,int b)
{
    return a>b?a:b;
}
int min(int a,int b)
{
    return a<b?a:b;
}

#define MAXSUM -2147483647
int maxProduct(int* nums, int numsSize){
    int preMax=1,preMin=1,ans=MAXSUM;
    for(int i=0;i<numsSize;i++)
    {
        if(nums[i]<0)
        {
            int temp=preMax;
            preMax=preMin;
            preMin=temp;
        }
        preMax=max(preMax*nums[i],nums[i]);
        preMin=min(preMin*nums[i],nums[i]);
        ans=max(ans,preMax);
    }
    return ans;
}

24 驗證迴文字符串2

2020-5-19

給定一個非空字符串 s,最多刪除一個字符。判斷是否能成爲迴文字符串。

示例 1:
輸入: “aba”
輸出: True

示例 2:
輸入: “abca”
輸出: True
解釋: 你可以刪除c字符。

注意:字符串只包含從 a-z 的小寫字母。字符串的最大長度是50000。

貪心

bool ttt(char *s,int l,int h)
{
    for(int i=l,j=h;i<j;i++,j--)
    {
        if(s[i]!=s[j])return false;
    }
    return true;
}

bool validPalindrome(char * s){
    int n=strlen(s),flag=0;
    for(int i=0,j=n-1;i<j;i++,j--)
    {
        if(s[i]!=s[j]){
            return ttt(s,i,j-1)||ttt(s,i+1,j);
        }
    }
    return true;
}

在這裏插入圖片描述
好久沒有獨立AC且結果不錯了,雖然是個簡單題,但還是感動老馬555555

25 每個元音包含偶數次的最長子字符串

2020-5-20

給你一個字符串 s ,請你返回滿足以下條件的最長子字符串的長度:每個元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出現了偶數次。

示例 1:
輸入:s = “eleetminicoworoep”
輸出:13
解釋:最長子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 個,以及 0 個 a,u

提示:

  • 1<=s.length<=51051 <= s.length <= 5 *10^5
  • ss 只包含小寫英文字母。

前綴和+狀態壓縮

前綴和
在不重複遍歷子串的前提下,快速求出這個區間裏元音字母出現的次數,對於一個區間,我們可以用兩個前綴和的差值,得到其中某個字母的出現次數。
定義 pre[i][k]{pre}[i][k]表示在字符串前 ii 個字符中,第 kk個元音字母一共出現的次數.

位運算
將五個元音字母數目的奇偶性狀態用01表示,並儲存在一個5位的二進制數中,則會產生32中奇偶狀態,對應數字0~31,當數字相同時表示狀態相同,則兩個狀態所在位置之差爲一個次優解。

狀態更新:
0表示偶數個,1表示奇數個,當出現a時,用00001與當前的狀態做異或運算,最後一位若爲1則更新爲零,若爲0則更新爲1,其他位不變。

int findTheLongestSubstring(char * s){
    int n=strlen(s);
    int status=0,ans=0;
    int *pos=(int *)malloc(sizeof(int)*32);
    pos[0]=0;
    for(int i=1;i<32;i++)  //不知道咋賦初值爲-1
    {
        pos[i]=-1;
    }

    for(int i=0;i<n;i++){
        if(s[i]=='a')status ^= (1 << 0); 
        else if(s[i]=='e')status ^= (1 << 1);
        else if(s[i]=='i')status ^= (1 << 2);
        else if(s[i]=='o')status ^= (1 << 3);
        else if(s[i]=='u')status ^= (1 << 4);
        if (pos[status]!=-1) {
            ans = fmax(ans, i + 1 - pos[status]);
        } 
        else {
            pos[status] = i + 1;
        }
    }
    return ans;
}

剪枝

int ans = 0;
int isOK(int *map){     //0,4,8,14,20分別對應5個元音字母
    if (map[0] % 2 == 0 && map[4] % 2 == 0 && 
        map[8] % 2 == 0 && map[14] % 2 == 0 && 
        map[20] % 2 == 0) {
        return 1;
    } else {
        return 0;
    }
}

void left2right(int *map1, int *map2, char *s)
{
    int sLen = strlen(s);
    int left = 0;   //左指針初始指向首位
    int right = sLen - 1;   //右指針初始指向末尾
    
    while (left <= right) { 
        if ((right - left) < (ans - 1)) {   //當左右指針間距小於當前答案時,剪枝
            break;
        }
        while (left <= right) {     //在一輪循環中,右指針固定,移動左指針
            if (isOK(map1) == 1) {      //如果滿足條件就更新答案,並跳出本輪,接下來移動右指針進入下一輪
                ans = fmax(ans, right - left + 1);
                break;
            } else {    //左指針不斷右移,把字母從統計表中刪去
                map1[s[left] - 'a']--;
                left++;
            }
            if ((right - left) < (ans - 1)) {   //當左右指針間距小於當前答案時,剪枝
                break;
            }
        }
        //此時一輪搜索結束,當前右指針所在位置的最長子串已經找到並更新到ans
        //重置左指針指向開頭,右指針左移一位;進入下一輪
        left = 0;
        map2[s[right] - 'a']--;  //去掉right指針指向的字母,對應的數量減一
        right--;
        memcpy(map1, map2, sizeof(int)*26);  //將2複製給1
    }
    return;
}

int findTheLongestSubstring(char * s){
    int map[26] = {0};
    int map1[26] = {0};
    int map2[26] = {0};
    int sLen = strlen(s);

    //統計每個字母出現的次數
    for (int i = 0; i < sLen; i++) {
        map[s[i] - 'a']++;
    }

    ans = 0; 
    memcpy(map1, map, sizeof(int)*26);
    memcpy(map2, map, sizeof(int)*26);
    left2right(map1, map2, s);

    return ans;
}

回顧

  • 奇偶個數校驗->位運算,異或
  • 遇到有限的參數(小於20個)表狀態, 想到二進制狀態壓縮 (bitmask)
  • 遇到求最長的連續子串使得和爲k想到 前綴和 加哈希表記錄第一次出現某一狀態的位置。

26 尋找重複數

2020-5-26

給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。

示例 1:
輸入: [1,3,4,2,2]
輸出: 2

示例 2:
輸入: [3,1,3,4,2]
輸出: 3

說明:
不能更改原數組(假設數組是隻讀的)。
只能使用額外的 O(1)O(1) 的空間。
時間複雜度小於 O(n2)O(n^2)
數組中只有一個重複的數字,但它可能不止重複出現一次。

二分法

  • 定義cnt[i]cnt[i]表示數組中小於等於ii的數的個數
  • 對於每個cnt[i]cnt[i],可以在O(n)O(n)時間內求得
  • 對於遍歷ii[1,n]i,i\in[1,n],使用二分法,如果出現 cnt[i]>icnt[i]>i,則重複的數字一定小於等於當前數字
int findDuplicate(int* nums, int numsSize){
    int left=1,right=numsSize-1,ans;
    while(left<=right){
        int mid=(left+right)/2;
        int cnt=0;
        for(int i=0;i<numsSize;i++){
            if(nums[i]<=mid)cnt++;
        }
        if(cnt<=mid){
            left=mid+1;
        }
        else{
            right=mid-1;
            ans=mid;
        }
    }
    return ans;
} 

時間複雜度:O(nlogn)O(nlogn)
空間複雜度:O(1)O(1)

快慢指針

https://leetcode-cn.com/problems/find-the-duplicate-number/solution/qian-duan-ling-hun-hua-shi-tu-jie-kuai-man-zhi-z-3/

int findDuplicate(int* nums, int numsSize){
    int slow = 0, fast = 0;
    do {
        slow = nums[slow];
        fast = nums[nums[fast]];
    } while (slow != fast);
    slow = 0;
    while (slow != fast) {
        slow = nums[slow];
        fast = nums[fast];
    }
    return slow;
} 

時間複雜度:O(n)O(n)
空間複雜度:O(1)O(1)

27 和可被 K 整除的子數組

2020-5-27

給定一個整數數組 A,返回其中元素之和可被 K 整除的(連續、非空)子數組的數目。

示例:
輸入:A = [4,5,0,-2,-3,1], K = 5
輸出:7
解釋:有 7 個子數組滿足其元素之和可被 K = 5 整除:[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

提示:
1 <= A.length <= 30000
-10000 <= A[i] <= 10000
2 <= K <= 10000

數據結構 哈希表

struct hashNode {
    int key;
    int value;
    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].value = 0;
        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;
        data->value = 1;
        return;
    }
    
    while (data != NULL) {//while循環要考慮當前節點,不能從data->next開始
        if (data->key == key) { //當前鏈表已存在該key
            data->value++;
            return;
        } 
        
        if (data->next == NULL) { //爲後續添加Node做準備
            break;
        }
        data = data->next; //鏈表next節點
    }

    /* 添加節點 */
    newNode = (struct hashNode *)malloc(sizeof(struct hashNode));
    newNode->key = key;
    newNode->value = 1;
    newNode->next = NULL;
    data->next = newNode;

    return;
}

//獲得鍵爲key的鍵值
int Get(int key, struct hashMap * hashMapInfo)
{
    int count = 0;
    int pos = abs(key) % hashMapInfo->size; //選擇hash桶
    struct hashNode *data = &hashMapInfo->data[pos];

    while (data != NULL) { /*遍歷鏈表,匹配key*/
        if (data->key == key) {
            count = data->value;
            break;
        }
        data = data->next;
    }
    return count;
}

暴力窮舉

int subarraysDivByK(int* A, int ASize, int K){
	int count=0;
    for(int i=0;i<ASize;i++){
        int sum=0;
        for(int j=i;j<ASize;j++){
            sum+=A[j];
            if(sum%K==0)count++;
        }
    }
    return count;
}

時間複雜度:O(n2)O(n^2)
空間複雜度:O(1)O(1)

前綴和+哈希

  1. 涉及到子數組的問題,使用前綴和來減少重複的求和計算
  2. 同餘定理:(pre[i]pre[j])%K==0pre[i]%K==pre[j]%K(pre[i]-pre[j])\%K==0 \Rightarrow pre[i]\%K==pre[j]\%K
  3. 類似於“ 和爲K的子數組”,將pre[i]%Kpre[i]\%K作爲鍵,出現的次數作爲鍵值,遍歷A依次計算pre[i]%Kpre[i]\%K,並將此key對應的哈希表的鍵值加到count中,並將key放到哈希表中,如果是第一次出現,自然爲零,若是第二次出現,count=1;若是第三次出現,count=3,A1-A2、A1-A3、A2-A3
  4. 當被除數爲負數時取模結果爲負數,需要糾正
int subarraysDivByK(int* A, int ASize, int K){
    struct hashMap *hashMapInfo;
    int count=0,sum = 0;  
    hashMapInfo = hashCreat(ASize); 
    Put(0, hashMapInfo); //初始化爲0,這個很重要,後續sum=k時需要
    for (int i = 0; i <ASize; i++) {
        sum+=A[i];
        int key=(sum%K+K)%K;  
        count += Get(key, hashMapInfo);
        Put(key, hashMapInfo);
    }
    return count;
}

時間複雜度:O(n)O(n)
空間複雜度:O(min(n,K))O(min(n,K))

在遍歷結束後再遍歷哈希表,用排列組合的方法來統計答案

int subarraysDivByK(int* A, int ASize, int K){
    struct hashMap *hashMapInfo;
    int count=0,sum = 0;  
    hashMapInfo = hashCreat(ASize); 
    Put(0, hashMapInfo); //初始化爲0,這個很重要,後續sum=k時需要
    for (int i = 0; i <ASize; i++) {
        sum+=A[i];
        int key=(sum%K+K)%K;  
        Put(key, hashMapInfo);
    }
    for(int key=0;key<K;key++)
    {
        int w=Get(key, hashMapInfo);
        count+=w*(w-1)/2;
    }
    return count;
}

前綴和+數組

餘數的範圍可預期,用數組更直觀

int subarraysDivByK(int* A, int ASize, int K){
    int* res = (int*)malloc(sizeof(int)*K);
    memset(res,0,K*sizeof(int));
    res[0] = 1;
    int cnt = 0,m = 0,sum = 0;
    for(int i = 0 ; i < ASize ;i++){
        sum+=A[i];
        m = (sum%K + K)%K;
        res[m]++;
    }
    for(int j = 0 ; j < K ;j++){
        if(res[j] >= 2){
            cnt+=res[j]*(res[j]-1)/2;
        }
    }
    return cnt;
}

28 字符串解碼

2020-5-28

給定一個經過編碼的字符串,返回它解碼後的字符串。
編碼規則爲: k[encoded_string],表示其中方括號內部的 encoded_string 正好重複 k 次。注意 k 保證爲正整數。

你可以認爲輸入字符串總是有效的;輸入字符串中沒有額外的空格,且輸入的方括號總是符合格式要求的。此外,你可以認爲原始數據不包含數字,所有的數字只表示重複的次數 k ,例如不會出現像 3a 或 2[4] 的輸入。

示例:
s = “3[a]2[bc]”, 返回 “aaabcbc”.
s = “3[a2[c]]”, 返回 “accaccacc”.
s = “2[abc]3[cd]ef”, 返回 “abcabccdcdcdef”.

思路

  1. 不爲右括號,入棧
  2. 否則,while (top(S)!=’[’) 出棧,存入數組
  3. 數組翻轉
  4. 左括號出棧
  5. 獲取左括號前的數字
  6. 重複存入數組
  7. 數字出棧
  8. 重複後的數組入棧

#define MaxSize 10000
typedef struct stack {
    char data[MaxSize];
    int top;
}Stack;

void initilize(Stack *S) { 
    S->top = -1;
}

void push(char x,Stack *S) { 
    S->top=S->top+1;
    S->data[S->top] = x;
}

void pop(Stack *S) {
    S->top=S->top-1; 
}

char top(Stack *S) {
    return S->data[S->top];
}

int empty(Stack *S) { 
    return S->top==-1;
}

char* reverse(char * s)
{
    for(int i = 0, j = strlen(s) - 1; i < j; i++, j--){
        char tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }
    return s;
}

char * decodeString(char * s){
    Stack * S = (Stack*)malloc(sizeof(Stack));
    initilize(S);

    int ansLength;
    for(int i=0;i<strlen(s);i++)
    {
        //不是右括號就一直入棧
        if(s[i]!=']'){
            push(s[i],S);
        }
        else{            
            char* temp=(char *)malloc(MaxSize*sizeof(char));//臨時數組,保存括號中間的字符
            int k=0;  //臨時數組的大小
            while (top(S)!='['){
                temp[k++]=top(S);//棧頂元素存入臨時數組
                pop(S);//棧頂元素出棧
            }
            temp[k]='\0';//尾部添加\0轉成字符串
            temp=reverse(temp);

            pop(S);//左括號出棧
            int repeat=0;
            int d=0;  //處理一位以上的數字
            while(!empty(S)&&top(S)>='0'&&top(S)<='9'){
                 repeat+=(top(S)-'0')*pow(10,d);   //獲取左括號前的數字
                 pop(S);//數字出棧
                 d++;
            } 

            for(int i=0;i<repeat;i++){
                for(int j=0;j<k;j++)
                {
                    push(temp[j],S);
                }
            }         
        }   
    }
    push('\0',S);
    return S;
}

回顧

  • 思路清晰,代碼寫不出來,555
  • 棧的數據結構中,對應函數的形參需要加*,否則改變不了對應的值,同時變成指針之後要用箭頭而不是點
  • S->top = -1,這樣第一個入棧的數據對應的top就是0
  • 要考慮到兩位及其以上的數字,使用指數和加法還原數字
  • 尾部添加\0轉成字符串
  • 遞歸下次再看,下次一定

29 打家劫舍

2020-5-29

你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。給定一個代表每個房屋存放金額的非負整數數組,計算你不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額。

示例 1:
輸入: [1,2,3,1]
輸出: 4
解釋: 偷竊 1 號房屋 (金額 = 1) ,然後偷竊 3 號房屋 (金額 = 3)。偷竊到的最高金額 = 1 + 3 = 4 。

示例 2:
輸入: [2,7,9,3,1]
輸出: 12
解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接着偷竊 5 號房屋 (金額 = 1)。偷竊到的最高金額 = 2 + 9 + 1 = 12 。

動態規劃

對於當前房屋i,有兩種選擇

  • ii,不偷i1i-1,此時的收益是當前房屋加i2i-2的收益
  • 不偷,當前收益等於i1i-1的收益

狀態轉移方程爲:
dp[i]=max(dp[i2]+nums[i],dp[i1])dp[i]=max(dp[i−2]+nums[i],dp[i−1])

邊界條件爲:

dp[0]=nums[0]dp[0]=nums[0],只有一間房屋,則偷竊該房屋
dp[1]=max(nums[0],nums[1])dp[1]=max(nums[0],nums[1]),只有兩間房屋,選擇其中金額較高的房屋進行偷竊

int rob(int* nums, int numsSize){
    int *dp= (int*)malloc(sizeof(int)*(numsSize+1));
    if(numsSize==0)return 0;
    if(numsSize==1){
        return nums[0];
    }
    dp[0]=nums[0];
    dp[1]=fmax(nums[0],nums[1]);
    for(int i=2;i<numsSize;i++){
        dp[i]=fmax((dp[i-2]+nums[i]),dp[i-1]);
    }
    return dp[numsSize-1];
}

時間複雜度:O(n)O(n)
空間複雜度:O(n)O(n)

加滾動數組

int rob(int* nums, int numsSize){
    if(numsSize==0)return 0;
    if(numsSize==1){
        return nums[0];
    }
    int dp0=nums[0];
    int dp1=fmax(nums[0],nums[1]);
    for(int i=2;i<numsSize;i++){
        int temp=dp1;
        dp1=fmax((dp0+nums[i]),dp1);
        dp0=temp;
    }
    return dp1;
}

時間複雜度:O(n)O(n)
空間複雜度:O(1)O(1)

回顧

動態規劃,一看題就懵,一看答案就懂

30 柱狀圖中的最大矩形

2020-5-30
在這裏插入圖片描述

暴力超時

int largestRectangleArea(int* heights, int heightsSize){

    int max=0;
    for(int i=0;i<heightsSize;i++){
        int h=heights[i];
        int l=1;
        for(int j=i-1;j>=0;j--){
            if(heights[j]<h)break;
            l++;
        }
        for(int k=i+1;k<heightsSize;k++){
            if(heights[k]<h)break;
            l++;
        }
        max=h*l>max?h*l:max;
    }
    return max;

}

單調棧

  • 對於當前數heights[i]heights[i],計算左右兩側小於heights[i]heights[i]的柱子對應的角標,兩角標之差減一即爲當前高度對應的橫軸的長度
  • 分別從左到右、從右到左循環,計算左側與右側
  • 棧空:對於左側設爲-1,對於右側設爲heightsSizeheightsSize
  • 棧頂元素的高度大於等於當前元素的高度,出棧
  • 棧頂到棧底遞減
#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;
}

int largestRectangleArea(int* heights, int heightsSize){
    init();
    int *left=(int *)malloc((heightsSize+1)*sizeof(int));
    int *right=(int *)malloc((heightsSize+1)*sizeof(int));
    for(int i=0;i<heightsSize;i++){
        if(empty()){
            left[i]=-1;
        }else{
            while(!empty()&&heights[Top()]>=heights[i]){
                pop();
            }
            if(empty()){
                left[i]=-1;
            }else{
                left[i]=Top();
            } 
        }
        push(i);
    }
    init();
    for(int j=heightsSize-1;j>=0;j--){
        if(empty()){
            right[j]=heightsSize;
        }else{
            while(!empty()&&heights[Top()]>=heights[j]){
                pop();
            }
            if(empty()){
                right[j]=heightsSize;
            }else{
                right[j]=Top();
            } 
        }
        push(j);
    }
    int max=0;
    for(int k=0;k<heightsSize;k++){
        int sum=heights[k]*(right[k]-left[k]-1);
       // printf("%d %d\n",left[k],right[k]);
        if(sum>max)max=sum;
    }
    return max;
}

一次遍歷

沒出棧的是右邊沒有比他小的,則取默認值爲heightsSizeheightsSize

#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;
}

int largestRectangleArea(int* heights, int heightsSize){
    init();
    int *left=(int *)malloc((heightsSize+1)*sizeof(int));
    int *right=(int *)malloc((heightsSize+1)*sizeof(int));
    for(int w=0;w<heightsSize;w++){
        right[w]=heightsSize;
    }
    for(int i=0;i<heightsSize;i++){
        while(!empty()&&heights[Top()]>=heights[i]){
            right[Top()]=i;
            pop();
        }
        if(empty()){
            left[i]=-1;
        }else{
            left[i]=Top();
        } 
        push(i);
    }

    int max=0;
    for(int k=0;k<heightsSize;k++){
        int sum=heights[k]*(right[k]-left[k]-1);
        //printf("%d %d\n",left[k],right[k]);
        if(sum>max)max=sum;
    }
    return max;
}

31 對稱二叉樹

2020-5-31

給定一個二叉樹,檢查它是否是鏡像對稱的。
例如,二叉樹 [1,2,2,3,4,4,3] 是對稱的。
在這裏插入圖片描述
[1,2,2,null,3,null,3] 則不是鏡像對稱的:
在這裏插入圖片描述
如果同時滿足下面的條件,兩個樹互爲鏡像:

  • 它們的兩個根結點具有相同的值
  • 每個樹的右子樹都與另一個樹的左子樹鏡像對稱

遞歸

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

bool check(struct TreeNode* p,struct TreeNode* q){
    if (!p && !q) return true;  //同時爲空
    if (!p || !q) return false;// 有一個不爲空
    return p->val==q->val&&check(p->left,q->right)&&check(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
    return check(root,root);
}

假設樹上一共 nn個節點
時間複雜度:O(n)O(n)
空間複雜度: O(n)O(n)

迭代 隊列

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

#define MaxSize 10000
typedef struct queue {
    struct TreeNode* data[MaxSize];
    int front;
    int rear;
}Queue;

Queue Q;

void initilize() { 
    Q.front = 0;
    Q.rear = 0;
}

void push(struct TreeNode* root) { 
    Q.data[++Q.rear] = root;
}

struct TreeNode* pop() { 
    return Q.data[++Q.front];
}

int empty() { 
    return Q.rear == Q.front;
}

bool check(struct TreeNode* p,struct TreeNode* q){
    initilize();
    push(p);
    push(q);
    while(!empty()){
        struct TreeNode* u=pop();
        struct TreeNode* v=pop();
        if (!u && !v) continue;
        if((!u||!v)||u->val!=v->val)return false;
        push(u->left);
        push(v->right);
        push(u->right);
        push(v->left);
    }
    return true;
}
bool isSymmetric(struct TreeNode* root){
    return check(root,root);
}

時間複雜度:O(n)O(n)
空間複雜度:O(n)O(n)

End 基礎支持

calloc函數

函數原型:void* calloc(unsigned int num,unsigned int size);

功能:在內存的動態存儲區中分配num個長度爲size的連續空間,函數返回一個指向分配起始地址的指針;如果分配不成功,返回NULL。

一般使用後要使用 free(起始地址的指針) 對內存進行釋放,不然內存申請過多會影響計算機的性能。如果使用過後不清零,還可以使用該指針對該塊內存進行訪問。

malloc函數

函數原型爲void *malloc(unsigned int size);

功能:在內存的動態存儲區中分配一個長度爲size的連續空間。函數的返回值是分配區域的起始地址,或者說,此函數是一個指針型函數,返回的指針指向該分配域的開頭位置。如果分配成功則返回指向被分配內存的指針(此存儲區中的初始值不確定),否則返回空指針NULL。

當內存不再使用時,應使用free()函數將內存塊釋放。函數返回的指針一定要適當對齊,使其可以用於任何數據對象。

動態申請二維數組

https://blog.csdn.net/fengxinlinux/article/details/51541003


  1. 原因一:沒有函數聲明,且函數定義在主函數之後;原因二:頭文件的被循環引用,在引用時考慮清楚包含順序; 原因三:頭文件函數聲明和函數定義參數不同;原因四:函數使用的參數類型是自定義類型(如結構體),而自定義類型的定義在函數的聲明和函數定義之間,由於在函數聲明時,結構體並沒有被定義,不被系統識別爲結構體,而後面定義函數時,結構體已經定義,系統將其識別爲結構體,導致系統認爲聲明和定義使用 的是不同的參數類型;所以纔會出現上述問題; ↩︎

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