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