leetcode数组题
数组算法
完美洗牌算法
c语言随机数如何产生:
利用srand((unsigned int)(time(NULL))是一种方法,因为每一次运行程序的时间是不同的。
在C语言里所提供的随机数发生器的用法:现在的C编译器都提供了一个基于ANSI标准的伪随机数发生器函数,用来生成随机数。它们就是rand()和srand()函数。这两个函数的工作过程如下:
1) 首先给srand()提供一个种子,它是一个unsigned int类型,其取值范围从0~65535;
2) 然后调用rand(),它会根据提供给srand()的种子值返回一个随机数(在0到32767之间)
3) 根据需要多次调用rand(),从而不间断地得到新的随机数;
4) 无论什么时候,都可以给srand()提供一个新的种子,从而进一步“随机化”rand()的输出结果。
下面是0~32767之间的随机数程序:
#include <stdlib.h> #include <stdio.h> #include <time.h>//使用当前时钟做种子 void main( void ) { int i; srand( (unsigned)time( NULL ) ); //初始化随机数 for( i = 0; i < 10;i++ ) //打印出10个随机数 printf( " %d\n", rand() ); }
完美洗牌算法:
#include <stdio.h> #include <stdlib.h> #include <time.h>//使用当前时钟做种子 int main () { int arr[10] ={2,5,3,23,5,4,22,44,776,45}; int temp; int index; int i; srand((unsigned)time(NULL));//初始化随机数 for(i=10-1;i>=0;i--) { temp = arr[i]; index = rand() %(i + 1);//生成0-i的随机数 //rand() % 10:生成[0,9]的整数随机数 //rand() % 10+5:生成[5,14]的整数随机数 arr[i] = arr[index]; arr[index] = temp; } for(i=0;i<10;i++) { printf("%d\n",arr[i]); } return 0; }
窗口大小为K的最大子数组和:利用滑动窗口
#include <stdio.h> #define k 3 //定义滑动窗口的大小 int main () { int list[15] = {5,23,86,21,43,67,45,34,58,23,102,123,11,22,1}; int i = 0;//数组下标 int j; //滑动窗口的起始头 int sum = 0; //临时结果 int maxsum = sum; //最后的结果 while(i+k-1<15){for(j = i;j<i+k;j++) { sum = sum +list[j]; } if(sum>maxsum) maxsum = sum; sum = 0; i++; } printf("%d\n",maxsum); }
寻找最小的k个数
假定最小的k个数,遍历数组以后的n-k个数,依次替换掉这k个数里的最大值。
#include <stdio.h> #define size 2 //k个数 int arr[10] ={2,5,3,23,5,4,22,44,776,45}; int minarr[size] = {2,5}; int Getmaxindex(){ int min = minarr[0]; int i =1; int index = 0; for(;i<size;i++) { //找到最大值 if(minarr[i]>min){ min = minarr[i]; index = i; } } return index; } int main () { int maxnumindex; //定义每次从minarr返回最大值的下标 int maxnum; //定义minarr中的最大值 int i = size -1; //继续从size-1的位置开始遍历往后的元素 while(i<10){ //得到最大值下标 maxnumindex = Getmaxindex(minarr); //得到最大值 maxnum = minarr[maxnumindex]; //继续遍历 //如果继续遍历得到的值比minarr数组中最大的值大,将这个最大值替换成这个值 if(arr[i]<maxnum){ minarr[maxnumindex] = arr[i]; } i++; } for(i = 0; i<size;i++){ printf("%d\n",minarr[i]); } return 0; }
寻找和为定值的两个数:有序数组与双向滑动指针
#include <stdio.h> int main () { int sum = 124; int list[15] = {5,23,86,21,43,67,45,34,58,23,102,123,11,22,1}; //对数组进行选择排序:从小到大//////// int i, j, temp,min; for(i = 0; i< 15;i++) { //令最小值等于无序区的第一个值 min = list[i]; //找到无序区的最小值 for(j = i; j<15;j++) { if(min>list[j]) { temp = min; min = list[j]; list[j] = temp; } } list[i] = min; } //for (i = 0;i < 15; i++) // printf("%d\n",list[i]); ///////////////////// ///有序:从小到大的序列中找到满足x+y =sum的x与y元素// int start = 0; int end = 14; int currsum = 0; while(start<end){ currsum = list[start] +list[end]; if(currsum == sum) { printf("currsum == sum:%d+%d=%d\n",list[start],list[end],currsum); //i++; //j--; //寻找其它x与y可以去掉这两行注释 break; }else{ //如果currsum比sum小,只需要移动start指针 if(currsum < sum){start++;} //如果currsum比sum大,只需要移动end指针 else{end--;} } } }
数组中出现次数超过一半的数字
如果一个数出现的次数超过数组一半的长度,那么就是说出现的次数比其他所有数字出现的次数还要多。因此我们可以考虑保存2个值,一个是数组中的一个数,一个是数的次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1,如果不同则次数减1。如果次数为0了这保存当前遍历到的数,并把次数设为1。遍历完整个数组之后,返回当前保存的数字,即是我们要找的数字。
#include <stdio.h> int numbers[] = {3,3,2}; int result = 3; int MoreThanHalfNum(int length) { int count = 1 ; int i; //从下标为1开始遍历 for (i = 1 ;i < length ; i++) { if (numbers[i] == result) { count++; } else { count-- ; } if (count == 0) { result = numbers[i] ; count++ ; } } return count; } int main() { int count = MoreThanHalfNum(3); if(count>0){ printf("出现次数超过一半:%d",result); }else if(count==0){ printf("出现次数与其它所有数相等:%d\n",result); }else{ printf("没有"); } return 0; }
寻找二维数组递增数组的一个数
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
#include <stdio.h> int matrix[5][5] = { {1,4,7,11,15}, {2,5,8,12,19}, {3,6,9,16,22}, {10,13,14,17,24}, {18,21,2326,30}, }; int find(int num){ int r = 0,c = 5 - 1;//从右上角开始 while (r <= 5 - 1 && c >= 0) { if (num == matrix[r][c]) { return 1; } else if (num > matrix[r][c]) { r++; } else { c--; } } return 0; } int main () { int flag = find(17); printf("%d\n",flag); return 0; }
奇偶数排序
给定一个整数数组,请调整数组中数的顺序,使得所有奇数位于数组的前半部分,偶数位于后半部分。
使用两个指针,一个指向头,一个指向尾,两个指针从数组头部和尾部向数组中间一段,如果头指针指向的是偶数,尾指针指向的是奇数,就交换两个数。
#include <stdio.h> //判断奇偶数 int isoaddnumber(int num){ return num&1; } // void OddEvenSort(int *pData,int length){ //头指针 int *pstart = pData; //尾指针 int *pend = pData + length - 1; int temp; while(pstart < pend){ //如果pstart指针指向的时奇数,正常,向右移 if(isoaddnumber(*pstart)){ pstart ++; } //如果pend指针指向的时偶数,正常,向左移 else if(isoaddnumber(*pend)==0){ pend --; }
//都不正常则交换两个数 else{ temp = *pstart; *pstart = *pend; *pend = temp; } } } int main() { int numbers[] = {3,2,8,56,34,5,9,32,23}; int i =0; OddEvenSort(numbers,9); for(;i<9;i++) printf("%d\n",numbers[i]); return 0; }
荷兰国旗
将n个红白蓝标题颜色的小球排序为,红、白、蓝,假设红为0,白为1,蓝为2
输入一组{0,1,2,1,2,0,2,0,1,2,0,1,1},返回{0,0,0,0,1,1,1,1,2,2,2,2}
#include <stdio.h> void Sort(int *pData,int length){ //头指针 int *pstart = pData; //尾指针 int *pend = pData + length - 1; //中指针 int *pcurrent = pData; //临时变量 int temp; while(pcurrent <= pend){ //current指针指向元素为0 //与start指针所指元素交换,current++,start++ if(*pcurrent ==0){ temp = *pstart; *pstart = *pcurrent; *pcurrent = temp; pstart ++; pcurrent++; } //current指针指向元素为1,不做任何交换,current++ else if(*pcurrent ==1){ pcurrent ++; } else{//current指针指向元素为2,与end指针所指元素交换,current不动,end-- temp = *pcurrent; *pcurrent = *pend; *pend = temp; pend--; } }
} int main() { int numbers[] = {0,1,2,1,2,0,2,0,1,2,0,1,1}; int i =0; Sort(numbers,12); for(;i<12;i++) printf("%d\n",numbers[i]); return 0; }
删除有序数组中的重复值
如何去除有序数组的重复元素
我们知道对于数组来说,在尾部插入、删除元素是比较高效的,时间复杂度是 O(1),但是如果在中间或者开头插入、删除元素,就会涉及数据的搬移,时间复杂度为 O(N),效率较低。
所以对于一般处理数组的算法问题,我们要尽可能只对数组尾部的元素进行操作,以避免额外的时间复杂度。
这篇文章讲讲如何对一个有序数组去重,先看下题目:
显然,由于数组已经排序,所以重复的元素一定连在一起,找出它们并不难,但如果毎找到一个重复元素就立即删除它,就是在数组中间进行删除操作,整个时间复杂度是会达到 O(N^2)。而且题目要求我们原地修改,也就是说不能用辅助数组,空间复杂度得是 O(1)。
其实,对于数组相关的算法问题,有一个通用的技巧:要尽量避免在中间删除元素,那我就想先办法把这个元素换到最后去。这样的话,最终待删除的元素都拖在数组尾部,一个一个 pop 掉就行了,每次操作的时间复杂度也就降低到 O(1) 了。
按照这个思路呢,又可以衍生出解决类似需求的通用方式:双指针技巧。具体一点说,应该是快慢指针。
我们让慢指针 slow
走左后面,快指针 fast
走在前面探路,找到一个不重复的元素就告诉 slow
并让 slow
前进一步。这样当 fast
指针遍历完整个数组 nums
后,nums[0..slow]
就是不重复元素,之后的所有元素都是重复元素。
int removeDuplicates(int[] nums) { int n = nums.length; if (n == 0) return 0; int slow = 0, fast = 1; while (fast < n) { if (nums[fast] != nums[slow]) { slow++; // 维护 nums[0..slow] 无重复 nums[slow] = nums[fast]; } fast++; } // 长度为索引 + 1 return slow + 1; }
看下算法执行的过程:
再简单扩展一下,如果给你一个有序链表,如何去重呢?其实和数组是一模一样的,唯一的区别是把数组赋值操作变成操作指针而已:
ListNode deleteDuplicates(ListNode head) { if (head == null) return null; ListNode slow = head, fast = head.next; while (fast != null) { if (fast.val != slow.val) { // nums[slow] = nums[fast]; slow.next = fast; // slow++; slow = slow.next; } // fast++ fast = fast.next; } // 断开与后面重复元素的连接 slow.next = null; return head; }