数据结构---数组(3)

1、寻找最小的K个数(剑指offer-30)

题目:输入n个整数,输出其中最小的K个数

例如,输入1、2、3、4、5、6、7、8这8个数字,则最小的4个数字为1、2、3、4。

解题思路一:
我们通过快排找到第k个数,然后比他的小的都在左边,比他大的都在右边。

	// 一次快速排序
	public static int Partition(int arr[], int low, int high) {
		int pivotkey = arr[low];
		while (low < high) {
			while (low < high && arr[high] >= pivotkey)
				high--;
			arr[low] = arr[high];
			while (low < high && arr[low] <= pivotkey)
				low++;
			arr[high] = arr[low];
		}
		arr[low] = pivotkey;
		return low;
	}

	// 最小的k个数
	public static void getLeastNumbers(int[] arry, int length, int k) {
		if (arry == null || length <= 0 || k > length || k <= 0)
			return;

		int start = 0;
		int end = length - 1;
		int index = Partition(arry, start, end);

		while (index != k - 1) {
			if (index > k - 1)
				index = Partition(arry, start, index - 1);
			else
				index = Partition(arry, index + 1, end);
		}

		for (int i = 0; i < k; i++) {
			System.out.print(arry[i] + " ");
		}
	}

解题思路二:(特别适合处理海量数据)

容量为K的最大堆存储最先遍历的K个数,并假设它们即是最小的K个数,建堆需要O(k)。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,否则不更新堆。这样下来,总费时O(k+(n-k)*logk) = O(nlogk)。

	//堆来实现最小的k个数
	public static void getLeastNumbers1(int[] arr,int length, int k) {  
		  
		if (arr == null || length <= 0 || k > length || k <= 0)
			return;		
		
		int tmp[] = new int[k + 1];  
	    tmp[0] = -1;  
	    for (int i = 0; i < k; i++) {  
	        tmp[i + 1] = arr[i];  
	    }  	    
	    
	    // 构建大根堆:O(n)  
	    for (int i = k / 2; i >= 1; i--) {  
	        makeMaxRootHeap(tmp, i, k);  
	    }  	    
	    
	    // 重建:O(nlogn)  
	    for (int i = k; i < arr.length; i++) {  
	       if(arr[i]<tmp[0]){
	    	   tmp[0]=arr[i];
	    	   makeMaxRootHeap(tmp, 1, k); 
	       }	         
	    }  
	    //打印
	    for (int i = 1; i < tmp.length; i++) {  
	       System.out.print(tmp[i]+" "); 
	    }  
	    
	}  
	  
	
	//构建大根堆
	public static void makeMaxRootHeap(int[] arr, int low, int high) {  
	    int tmp = arr[low];  
	    int j;  
	    for (j = 2 * low; j <= high; j *= 2) {  
	        if (j < high && arr[j] < arr[j + 1]) {  
	            j++;  
	        }  
	        if (tmp >= arr[j]) {  
	            break;  
	        }  
	        arr[low] = arr[j];  
	        low = j;  
	    }  
	    arr[low] = tmp;  
	}

2、连续子数组的最大和(剑指offer-31)
题目:
输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间负责度为O(n)。
解析:
假如输入数组为{1,-2,3,10,-4,7,2,-5},我们尝试从头到尾累加其中的正数,初始化和为0,第一步加上1,此时和为1,第二步加上-2,此时和为-1,第三步加上3,此时我们发现-1+3=2,最大和2反而比3一个单独的整数小,这是因为3加上了一个负数,发现这个规律以后我们就重新作出累加条件:如果当前和为负数,那么就放弃前面的累加和,从数组中的下一个数再开始计数。

	// 求最大连续子序列和
	public static int FindGreatestSumOfSubArray(int arry[], int len) {
		if (arry == null || len <= 0)
			return -1;

		int start = 0, end = 0; // 用于存储最大子序列的起点和终点
		int currSum = 0; // 保存当前最大和
		int p = 0;// 指针,用于遍历数组。
		int greatestSum = 0x80000000;// 保存全局最大和
		for (int i = 0; i < len; i++) {
			if (currSum < 0)// 如果当前最大和为负数,则舍弃前面的负数最大和,从下一个数开始计算
			{
				currSum = arry[i];
				p = i;
			} else {
				currSum += arry[i];// 如果当前最大和不为负数则加上当前数
			}

			if (currSum > greatestSum)// 如果当前最大和大于全局最大和,则修改全局最大和
			{
				greatestSum = currSum;
				start = p;
				end = i;
			}
		}
		System.out.println("最大子序列位置:" + start + "--" + end);
		return greatestSum;
	}

使用动态规划方法
解题思路:
如果用函数f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求出max(f[0...n])。我们可以给出如下递归公式求f(i)


这个公式的意义:
1、当以第(i-1)个数字为结尾的子数组中所有数字的和f(i-1)小于0时,如果把这个负数和第i个数相加,得到的结果反而不第i个数本身还要小,所以这种情况下最大子数组和是第i个数本身。
2、如果以第(i-1)个数字为结尾的子数组中所有数字的和f(i-1)大于0,与第i个数累加就得到了以第i个数结尾的子数组中所有数字的和。

其实上述两种方法的实现方式非常相似,只是解体思路不同而已。通常我们会使用递归的方式分析动态规划的问题,但是最终都会基于循环去写代码。


3、统计在从1到n的正整数中1出现的次数(剑指offer-32)

解法一:最直观的想法,求1到n中每个整数中1出现的次数,然后相加即可。而求每个十进制整数中1出现的次数,我们先判断这个数的个位数是否是1,如果这个数大于10,除以10之后再判断个位数是否为1,循环直至求出该整数包含1的个数。

	public static int NumberOf1(int n) {
		int i , j;
		int count = 0;
		for (i = 1; i <= n; i++) {
			j = i;
			while (j != 0) {
				if (j % 10 == 1)
					count++;
				j = j / 10;
			}
		}
		return count;
	}
解法二:归纳法寻找N的各位中包含1的规律:
如果当前位上的数字为0,则在该位上出现1的次数由更高位决定,等于更高位乘以当前位数(十位为10,百位为100);
如果当前位上的数字为等于1,则该位上出现1的次数由高位和低位共同决定:等于高位乘以当前位数加上低位数字加1;

如果当前位上的数字大于1,则该位上出现1的次数由高位决定:等于高位数字加1然后乘以当前的位数。

	public static int CountOne(int n) {
		int count = 0;
		int i = 1;
		int current = 0, after = 0, before = 0;
		while ((n / i) != 0) {
			current = (n / i) % 10;
			before = n / (i * 10);
			after = n - (n / i) * i;

			if (current > 1)
				count = count + (before + 1) * i;
			else if (current == 1)
				count = count + before * i + after + 1;
			else 
				count = count + before * i;

			i = i * 10;
		}
		return count;
	}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值