2020年8月 leetcode每日一题 C语言版本

1 恢复二叉搜索树

2020-8-8

二叉搜索树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
https://leetcode-cn.com/problems/recover-binary-search-tree/

中序遍历

  1. 中序遍历
  2. 找错误的节点:若 i < j i<j i<j a i > a i + 1 a_i>a_{i+1} ai>ai+1 a j > a j + 1 a_j>a_{j+1} aj>aj+1,那么对应被错误交换的节点即为 a i a_i ai对应的节点和 a j + 1 a_{j+1} aj+1对应的节点
  3. 交换错误的节点
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
//抄答案的
int len, max_size;

//中序遍历,遍历结果存储在数组nums中 
void inorder(struct TreeNode* root, int** nums) {
    if (root == NULL) {
        return;
    }
    inorder(root->left, nums);
    (*nums)[len++] = root->val;
    if (len == max_size) {   //数组越界处理
        max_size <<= 1;
        (*nums) = (int*)realloc((*nums), sizeof(int) * max_size);
    }
    inorder(root->right, nums);
}

//找交换的两个结点
int* findTwoSwapped(int* nums) {
    int x = -1, y = -1;
    for (int i = 0; i < len - 1; ++i) {
        if (nums[i + 1] < nums[i]) {
            y = nums[i + 1];
            if (x == -1) {
                x = nums[i];
            } else
                break;
        }
    }
    int* ret = (int*)malloc(sizeof(int) * 2);
    ret[0] = x, ret[1] = y;
    return ret;//返回需要交换的两个数
}

//前序递归更改结点的值
void recover(struct TreeNode* r, int count, int x, int y) {
    if (r != NULL) {
        if (r->val == x || r->val == y) {
            r->val = r->val == x ? y : x;
            if (--count == 0) {
                return;
            }
        }
        recover(r->left, count, x, y);
        recover(r->right, count, x, y);
    }
}

void recoverTree(struct TreeNode* root) {
    len = 0, max_size = 1;
    int* nums = (int*)malloc(sizeof(int));
    inorder(root, &nums);
    int* swapped = findTwoSwapped(nums);
    recover(root, 2, swapped[0], swapped[1]);
}

时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( N ) O(N) O(N)

空间优化
不使用数组存储中序遍历的结果,在中序遍历的过程中记录前一个被遍历的值,同时进行比较

void recoverTree(struct TreeNode* root) {
    struct TreeNode** stk = (struct TreeNode**)malloc(0);
    int stk_top = 0;
    struct TreeNode* x = NULL;
    struct TreeNode* y = NULL;
    struct TreeNode* pred = NULL;

    while (stk_top > 0 || root != NULL) {
        while (root != NULL) {
            stk_top++;
            stk = (struct TreeNode**)realloc(stk, sizeof(struct TreeNode*) * stk_top);
            stk[stk_top - 1] = root;
            root = root->left;
        }
        root = stk[--stk_top];
        if (pred != NULL && root->val < pred->val) {
            y = root;
            if (x == NULL) {
                x = pred;
            } else
                break;
        }
        pred = root;
        root = root->right;
    }
    int t = x->val;
    x->val = y->val, y->val = t;
}

Morris 中序遍历***

Morris 中序遍历

将非递归的中序遍历空间复杂度降为 O ( 1 ) O(1) O(1)
利用的是树的叶节点左右孩子为空(树的大量空闲指针),把节点信息存在了某个空节点(tail.right)从而避免了使用栈带来的空间开销
Morris算法实现前序中序后序遍历

/**
 * 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

struct TreeNode* getPredecessor(struct TreeNode *curr) {
    struct TreeNode *predecessor = curr;
    if (curr->left != NULL) {
        predecessor = curr->left;
        while (predecessor->right != NULL && predecessor->right != curr) {
            predecessor = predecessor->right;
        }
    }
    return predecessor;
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
    int *inorderArray=malloc(MAXSIZE*sizeof(int));
    *returnSize=0;
    struct TreeNode *curr = root;
    while (curr != NULL) {
        if (curr->left == NULL) {
            inorderArray[(*returnSize)++]=curr->val;
            curr = curr->right;
        } else {
            struct TreeNode *predecessor = getPredecessor(curr);
            if (predecessor->right == NULL) {
                predecessor->right = curr;
                curr = curr->left;
            } else if (predecessor->right == curr) {
                predecessor->right = NULL;
                inorderArray[(*returnSize)++]=curr->val;
                curr = curr->right;//这段的右孩子,是人为设置的
            }
        }
    }
    return inorderArray;
}

本题

void recoverTree(struct TreeNode* root) {
    struct TreeNode *x = NULL, *y = NULL, *pred = NULL, *predecessor = NULL;

    while (root != NULL) {
        if (root->left != NULL) {
            // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
            predecessor = root->left;
            while (predecessor->right != NULL && predecessor->right != root) {
                predecessor = predecessor->right;
            }

            // 让 predecessor 的右指针指向 root,继续遍历左子树
            if (predecessor->right == NULL) {
                predecessor->right = root;
                root = root->left;
            }
            // 说明左子树已经访问完了,我们需要断开链接
            else {
                if (pred != NULL && root->val < pred->val) {
                    y = root;
                    if (x == NULL) {
                        x = pred;
                    }
                }
                pred = root;

                predecessor->right = NULL;
                root = root->right;
            }
        }
        // 如果没有左孩子,则直接访问右孩子
        else {
            if (pred != NULL && root->val < pred->val) {
                y = root;
                if (x == NULL) {
                    x = pred;
                }
            }
            pred = root;
            root = root->right;
        }
    }
    int t = x->val;
    x->val = y->val, y->val = t;
}

2 计数二进制子串

2020-8-10

给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。

重复出现的子串要计算它们出现的次数。

示例 1 :
输入: “00110011”
输出: 6
解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。
请注意,一些重复出现的子串要计算它们出现的次数。另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。

愚蠢的穷举

int help(int *s,int l,int r){
    int n=r-l+1;
    if(n%2!=0)return 0;
    int sum_left=0;
    for(int j=0;j<n/2;j++){
        sum_left+=s[l+j];
    }
    int sum_right=0;
    for(int k=n/2;k<n;k++){
        sum_right+=s[l+k];
    }
    if(sum_right&&sum_left)return 0;
    for(int i=0;i<=n/2;i++){

        if(s[l+i]+s[r-i]!=1)return 0;
    }
    return 1;
}


int countBinarySubstrings(char * s){
    int ans=0;  
    int len=strlen(s);
    int num[len];
    for(int i=0;i<len;i++){
        num[i]=(int)(s[i]-'0');
    }
    for(int i=0;i<len;i++){
        for(int j=i+1;j<len;j++){
            if(help(num,i,j)){
                ans++;
            }
        }
    }
    return ans;
}

聪明的字符分组

int countBinarySubstrings(char * s){
    int len=strlen(s);
    //printf("%d  ",len);
    int num[len];
    memset(num, 0, sizeof(num));
    int k=0,m=0,ans=0;

    while(m<len){
        char t=s[m];
        int count=0;
        while(m<len&&s[m]==t){
            count++;
            m++;
        }
        num[k++]=count;
    }

    for(int i=0;i<len-1;i++){
        //printf("n= %d  num= %d\n",i,num[i]);
        ans+=fmin(num[i],num[i+1]);
    }
    return ans;
}

空间优化

int countBinarySubstrings(char * s){
    int len=strlen(s);
    int num=0;
    int k=0,m=0,ans=0;

    while(m<len){
        char t=s[m];
        int count=0;
        while(m<len&&s[m]==t){
            count++;
            m++;
        }
        ans+=fmin(num,count);
        num=count;
    }

    return ans;
}

大神版

用last来记录之前一种数字的个数, cur来记录当前数字的个数; 当last >= cur的时候, ans ++

int countBinarySubstrings(char * s){
    int len=strlen(s);
    int last=0,cur=1,ans=0;
    for(int i=1;i<len;i++){
        if(s[i] == s[i-1]) cur++;
        else{
            last=cur;
            cur=1;
        }
        if(last>=cur){
            ans++;
        }
    }
    return ans;
}

3 扫雷游戏

2020-8-20

给定一个代表游戏板的二维字符矩阵。 ‘M’ 代表一个未挖出的地雷,‘E’ 代表一个未挖出的空方块,‘B’ 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字(‘1’ 到 ‘8’)表示有多少地雷与这块已挖出的方块相邻,‘X’ 则表示一个已挖出的地雷。

现在给出在所有未挖出的方块中(‘M’或者’E’)的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板:

如果一个地雷(‘M’)被挖出,游戏就结束了- 把它改为 ‘X’。
如果一个没有相邻地雷的空方块(‘E’)被挖出,修改它为(‘B’),并且所有和其相邻的未挖出方块都应该被递归地揭露。
如果一个至少与一个地雷相邻的空方块(‘E’)被挖出,修改它为数字(‘1’到’8’),表示相邻地雷的数量。
如果在此次点击中,若无更多方块可被揭露,则返回面板。

示例 1:

输入:

[[‘E’, ‘E’, ‘E’, ‘E’, ‘E’],
[‘E’, ‘E’, ‘M’, ‘E’, ‘E’],
[‘E’, ‘E’, ‘E’, ‘E’, ‘E’],
[‘E’, ‘E’, ‘E’, ‘E’, ‘E’]]

Click : [3,0]

输出:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘M’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],
[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

在这里插入图片描述
示例 2:

输入:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘M’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],
[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

Click : [1,2]

输出:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘X’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],
[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

在这里插入图片描述
注意:

  • 输入矩阵的宽和高的范围为 [1,50]。
  • 点击的位置只能是未被挖出的方块 (‘M’ 或者 ‘E’),这也意味着面板至少包含一个可点击的方块。
  • 输入面板不会是游戏结束的状态(即有地雷已被挖出)。
  • 简单起见,未提及的规则在这个问题中可被忽略。例如,当游戏结束时你不需要挖出所有地雷,考虑所有你可能赢得游戏或标记方块的情况。

深度优先搜索

  1. 踩雷—变为X返回
  2. 未踩雷—数周围有多少个雷
  3. 周围有雷—变为雷的个数
  4. 周围无雷—对未点开的dfs递归
/**
 * 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().
 */
const int dir_x[8] = {0, 1, 0, -1, 1, 1, -1, -1};
const int dir_y[8] = {1, 0, -1, 0, 1, -1, 1, -1};

int n, m;

void dfs(char** board, int x, int y) {
    int cnt = 0;
    //统计8个方向的雷的个数
    for (int i = 0; i < 8; ++i) {
        int tx = x + dir_x[i];
        int ty = y + dir_y[i];
        if (tx < 0 || tx >= n || ty < 0 || ty >= m) {
            continue;
        }
        // 不用判断 M,因为如果有 M 的话游戏已经结束了
        cnt += board[tx][ty] == 'M';
    }
    if (cnt > 0) {
        // 规则 3
        board[x][y] = cnt + '0';
    } else {
        // 规则 2
        board[x][y] = 'B';
        for (int i = 0; i < 8; ++i) {
            int tx = x + dir_x[i];
            int ty = y + dir_y[i];
            // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了
            if (tx < 0 || tx >= n || ty < 0 || ty >= m || board[tx][ty] != 'E') {
                continue;
            }
            dfs(board, tx, ty);
        }
    }
}

char** updateBoard(char** board, int boardSize, int* boardColSize, int* click, int clickSize, int* returnSize, int** returnColumnSizes) {
    n = boardSize, m = boardColSize[0];
    int x = click[0], y = click[1];
    if (board[x][y] == 'M') {
        // 规则 1
        board[x][y] = 'X';
    } else {
        dfs(board, x, y);
    }
    *returnSize = n;
    **returnColumnSizes = malloc(sizeof(int*) * n);
    for (int i = 0; i < n; i++) {
        (*returnColumnSizes)[i] = m;
    }
    return board;
}

4 合并数组

给定长度为n的数组a,长度为m的数组b,长度为m+n的数组c,c的字符按照ab数组中任意一个数组最末端未被选择的数
如:
a 1 2 3
b 4 5 6
可能的c:
1 2 3 4 5 6
1 4 2 5 3 6
1 2 4 3 5 6
不可能的c:
1 2 3 5 6 4
1 2 5 3 6 4

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

int main() {
	int n, m;
	cin >> n >> m;
	int *a = (int*)malloc(n*sizeof(int));
	int* b = (int*)malloc(m * sizeof(int));
	int* c = (int*)malloc((n+m) * sizeof(int));
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	for (int i = 0; i < m; i++) {
		cin >> b[i];
	}
	for (int i = 0; i < m+n; i++) {
		cin >> c[i];
	}

	int i = 0, j = 0, k = 0;
	while (k < m + n) {
		if (c[k] == a[i])i++;
		else if(c[k] == b[j])j++;
		else {
			cout << "No";
			return 0;
		}
	}
	cout << "Yes";
	return 0;
}

5 子串最大的最小值

n个队员,第i个队员的能力值为ai,选择k个队员组成队伍,选择的队员需连续,队伍总能力值为各个队员能力的最小值,给定n个队员能力值的数组,输出选择1-n个队员的队伍的最大能力值

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

int maxMin(int* a, int n,int k) {
	int max = 0;
	//cout << "k= " << k << endl;
	for (int i = 0; i <= n - k; i++) {
		int min = 2147483647;
		//cout <<"i= "<< i<<endl;
		for (int j = i; j < i + k; j++) {
			//cout << "a[j]= " << a[j] << endl;
			if (a[j] < min) {
				min = a[j];
			}
		}
		//cout << "min= " << min << endl;
		if (max < min) {
			max = min;
		}
	}
	return max;
}
int main() {
	int n;
	cin >> n;
	int *a = (int*)malloc(n*sizeof(int));
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	for (int i = 0; i < n; i++) {
		//cout<<a[i];
	}
	for (int i = 1; i <= n; i++) {
		cout << maxMin(a, n, i)<<" ";
	}
	return 0;
}

6 重复的子字符串

2020-8-24

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成

穷举

  • 如果一个长度为 n n n 的字符串 s s s可以由它的一个长度为 n ′ n' n 的子串 s ′ s' s重复多次构成,那么 n n n 一定是 n ′ n' n 的倍数;
  • s ′ s' s一定是 s s s的前缀,对于任意的 i ∈ [ n ′ , n ) i \in [n', n) i[n,n),有 s [ i ] = s [ i − n ′ ] s[i] = s[i-n'] s[i]=s[in]
  • 子串至少需要重复一次,所以 n ′ n' n不会大于 n n n 的一半,我们只需要在 [ 1 , n 2 ] [1, \frac{n}{2}] [1,2n] 的范围内枚举 n ′ n' n
//抄的答案
bool repeatedSubstringPattern(char* s) {
    int n = strlen(s);
    for (int i = 1; i * 2 <= n; ++i) {
        if (n % i == 0) {
            bool match = true;
            for (int j = i; j < n; ++j) {
                if (s[j] != s[j - i]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                return true;
            }
        }
    }
    return false;
}
//我的代码
bool repeatedSubstringPattern(char * s){
    int n=strlen(s);
    char a=s[0];   //首字母 
    int r=1;  //子串右边界

    while(r<n){
        //printf("%d %c\n",r,s[r]);
        if(s[r]==a&&n%(r)==0){
            int flag=1;
            int n1=r;  //子串的长度
            int n2=n/n1;  //重复的个数
            
            for(int k=1;k<n2;k++){
                for(int j=0;j<n1;j++){
                   // printf("j= %d s[j]: %c\n",j,s[j]);
                   // printf("k= %d s[k]: %c\n",k*(n1-1)+j,s[k*(n1)+j]);
                    if(s[j]!=s[k*(n1)+j])flag=0;
                } 
            }
            if(flag==1)return true;
        }  
        r++;
        //printf("fffff   ");   
    }
    return false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值