[C语言题解]《算法零基础100讲》(第18讲) 线性枚举(二) - 统计法入门

一.知识讲解

1.线性遍历:线性遍历就是通过循环,将一个数组的数据按线性的方式依次遍历
例如:将一个数组内的数据全部打印一遍

void line(int* nums, int* numsSize){
    
    
	for(int i = 0; i < numsSize; i++){
    
    
		printf("%d ", nums[i]);
	}
	printf("\n");
}

2.计算十进制数据的位数
  我们可以通过辗转相除的方式,将一个十进制的数子拆分成一个个个位数,并将其统计下来,得到该十进制数为多少位。

int CountNumbers(int num){
    
    
	int count = 0;
	while(num){
    
    
		num /= 10;
		count++;
	}
	return count;
}

二.课后习题

2.1 统计位数为偶数的数字

1295. 统计位数为偶数的数字
在这里插入图片描述
在这里插入图片描述
这道题我们需要对数组进行线性遍历,并通过辗转相除的方式判断每个数是否为偶位数。
代码如下:

int findNumbers(int* nums, int numsSize){
    
    
    int ret = 0;//统计偶位数的个数
    for(int i = 0; i < numsSize; i++){
    
    
    	//统计nums[i]的位数
        int n = 0;
        //分解
        while(nums[i]){
    
    
            nums[i] /= 10;
            n++;
        }
        //判断
        if(n % 2 == 0){
    
    
            ret++;
        }
    }
    return ret;
}

在这里插入图片描述

2.2 有序数组中的单一元素

540. 有序数组中的单一元素
在这里插入图片描述
因为相同的数都是相邻的,所以我们可以直接从头开始遍历,每次检查前面一个数据与第二个数据是否相同。
代码如下:

int singleNonDuplicate(int* nums, int numsSize){
    
    
    int i = 0;
    for(; i < numsSize - 1; i += 2){
    
    
        if(nums[i] != nums[i + 1]){
    
    
            return nums[i];
        }
    }
    return nums[i];
}

在这里插入图片描述

2.3 调整数组顺序使奇数位于偶数前面

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
在这里插入图片描述
双指针法:
  我们可以定义一个数组ret[],定义两个变量left和right分别指向ret的首尾,在对数组nums进行遍历,如果位奇数,则放入ret[left++],为偶数则放入ret[right–]。
如图:
在这里插入图片描述

一次类推,我们便可将奇数放在左边,偶数放在右边。
代码如下:

int* exchange(int* nums, int numsSize, int* returnSize){
    
    
    *returnSize = numsSize;
    int* ret = (int*)malloc(sizeof(int) * numsSize);
    int left = 0;
    int right = numsSize - 1;
    for(int i = 0; i < numsSize; i++){
    
    
        if(nums[i] % 2){
    
    
            ret[left++] = nums[i];
        }
        else{
    
    
            ret[right--] = nums[i];
        }
    }
    return ret;
}

在这里插入图片描述

2.4 找到数组的中间位置

1991. 找到数组的中间位置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分析:
  这道题的意思是,找到一个数,该数左边的所有数之和,等于右边所有数之和,如果在左边界,则左边和为0,反之,右边和为0。
我们假设左边和为left,右边和为righ,中间值为mid,所有数和为total,则total = left + right + mid,则right = total - left - mid。
如果left == right,我们可以得到:

left * 2 == total - mid
通过此式,我们就很容易解决这道问题了。
  1. 首先计算所有数之和total。
  2. 然后开始从头遍历,用sum来统计前i - 1个数的和,如果sum * 2 = total - nums[i],则i为中间位置。
    代码如下:
int findMiddleIndex(int* nums, int numsSize){
    
    
    int total = 0;
    for(int i = 0; i < numsSize; i++){
    
    
        total += nums[i];
    }
    int sum = 0;
    for(int i = 0; i < numsSize; i++){
    
    
        if(2 * sum + nums[i] == total){
    
    
            return i;
        }
        sum += nums[i];
    }
    return -1;
}

在这里插入图片描述

2.5 寻找数组的中心下标

724. 寻找数组的中心下标
在这里插入图片描述
在这里插入图片描述
这道题和前一道题是一样的,解题方法也是一样的
代码如下:

int pivotIndex(int* nums, int numsSize){
    
    
    int total = 0;
    for(int i = 0; i < numsSize; i++){
    
    
        total += nums[i];
    }
    int sum = 0;
    for(int i = 0; i < numsSize; i++){
    
    
        if(2 * sum + nums[i] == total){
    
    
            return i;
        }
        sum += nums[i];
    }
    return -1;
}

在这里插入图片描述

2.6 删除有序数组中的重复项

26. 删除有序数组中的重复项
在这里插入图片描述
在这里插入图片描述
这道题用快慢双指针的方式
由于nums已成升序,所以该题也大大降低了难度。
思路:

  1. 定义两个指针,slow和fast,初始值为1。
  2. 判断fast[i]与fast[i - 1]是否相等,如果相等,则fast++,不相等则nums[slow++] = nums[fast++]。
  3. 最后在下标slow前的元素中,重复多余的数据全部覆盖,slow就是修改后数组的长度
    在这里插入图片描述
    代码如下:
int removeDuplicates(int* nums, int numsSize){
    
    
    if(numsSize == 0)
        return 0;
    
    int slow = 1;
    int fast = 1;

    while(fast < numsSize){
    
    
        if(nums[fast] != nums[fast - 1]){
    
    
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    return slow;
}

在这里插入图片描述

2.7 可被 5 整除的二进制前缀

1018. 可被 5 整除的二进制前缀
在这里插入图片描述
在这里插入图片描述
这道题给定一个只包含0和1的数组nums,我们需定义一个数组ret[],ret[i]表示由nums[0],nums[1]…nums[i]组成的二进制数转化为十进制后能否被5整除,能则ret[i] = true,
否则ret[i] = false。
但根据题目给定的范围,我们发现,将其转化为十进制会越界,3000长度的二进制数我们是无法计算的,所以我们需要在计算过程中对其进行mod处理。
代码如下:

bool* prefixesDivBy5(int* nums, int numsSize, int* returnSize){
    
    
    *returnSize = numsSize;
    bool* ret = (bool*)malloc(sizeof(bool) * numsSize);
    int dp = 0;
    for(int i = 0; i < numsSize; i++){
    
    
        dp = (dp * 2 + nums[i]) % 5;
        ret[i] = dp == 0;
    }
    return ret;
}

在这里插入图片描述

2.8 被K整除的最小整数

1015. 可被 K 整除的最小整数
在这里插入图片描述
在这里插入图片描述
这道题简单就简单在数字n只含有1,所以我们只需令n = 1,不断地n *= 10,n += 1,在判断能否整除k即可,但我们还需考虑的是有的数是溢出,有些数能够只含1的数整除的数很大,例如整除23的数的长度为22,也就是1111111111111111111111,这样的数字很明显已经超出我们所能计算的范围,所以我们还需对其进行mod操作。
代码如下:

int smallestRepunitDivByK(int k){
    
    
    long long n = 1;
    int len = 1;
    if(k % 2 == 0 || k % 5 == 0){
    
    
        return -1;
    }
    while(n < INT_MAX){
    
    
        if(n % k == 0){
    
    
            return len;
        }
        n %= k;
        n *= 10;
        n += 1;
        len++;
    }
    return -1;
}

在这里插入图片描述

2.9 哪种连续子字符串更长

1869. 哪种连续子字符串更长
在这里插入图片描述
在这里插入图片描述
分析:
  我们需要分别找出字符‘1’的最大连续长度和字符‘0’的最大连续长度,然后进行比较。
方法步骤:
  如果我们要找出‘1’的最大连续长度,我们可以定义两个变量max1和count1,max1用来记录最大连续长度,count1用来计算每段字符‘1’的长度。
  当s[i] == '1’时,count1++,当s[i] = '0’时,我们令count1=0,从新计算下一段,并把最长的一段记录到max1中
  字符‘0’的长度也是如此。
代码入下:

bool checkZeroOnes(char * s){
    
    
    int len = strlen(s);
    int max1 = 0, max0 = 0;//记录'1'和'0'最长的长度
    int count1 = 0, count0 = 0;//计算'1'和'0'每段的长度
    for(int i = 0; i < len; i++){
    
    
        if(s[i] == '0'){
    
    
            count0++;
        }
        else{
    
    
            count0 = 0;
        }

        if(s[i] == '1'){
    
    
            count1++;
        }
        else{
    
    
            count1 = 0;
        }
        //将最长的值记录到max0和max1中
        max0 = fmax(max0, count0);
        max1 = fmax(max1, count1);
    }
    if(max1 > max0){
    
    
        return true;
    }
    return false;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_53060585/article/details/121195111