时间效率
- 互联网想对时间效率格外的敏感,所以我们总是在需求迭代一定程度后去做优化。而且我们解决问题的时候,时间效率往往是一个考查的重点。因此我们平时编码过程中就必须不断的优化效率,追求完美的态度与能力。
- 首先是编码习惯,例如java中字符串操作我们不建议使用String的+ 运算符来完成。这样会产生非常多的String临时变量,造成空间与实际的浪费,更好的方式是用StringBuilder的Append方法完成字符串的拼接。
- 其次,即使是同一个算法用循环和递归两种思路实现时间效率也是不一样的。递归的本质是吧一个大的复杂问题拆解成两个或者多个小的简单问题,如果小问题中互相重叠,那么直接用递归实现,代码会更加简单,但是时间效率非常差,这时候,我们可以用递归的思路来分析,但是用数组(一维数组或者二维数组)来保存中间结果基于循环实现。绝大部分动态规划算法的分析和代码实现都是分这两个步骤。
- 再次,代码时间效率还体现在我们数据结构的掌握程度。
- 最后以如下提来举例:
数组中出现次数超过一半的数字
-
数组中有一个数字出现的次数超过数组长度的一半,找出这个数字,例如:输入一个长度为10 的数组{1,2,3,3,3,3,3,3,4,5,6,7}。由于数组3在数组中出现了6次,超过数组长度的一半因此输出3
-
解法一:看到题目的第一反应就是讲数组排序统计,自然很容易得出每个数字的次数。题目给出的数组没说排序
- 先排序。排序的时间复杂度O(nlogn)
- 接着统计O(n)
- 最直观的算法通常不是最优解,我们接着找更优的解法
-
解法二:题意中是一个特殊的数组,数组中有一个数字出现次数超过数组长度的一半。排序后,不管其中重复的数字怎么排,其中位于数组中间位置的数字比如就是出现超过一半的那个数字。
- 结论是我们需要找的就是统计学中的中位数。即长度为n 的数组,找出第n/2 大的数字,这个有成熟的O(N)算法得到数组中第K个大的数
-
分析:
- 我们在之前的排序算法文章:数据结构与算法–排序算法总结(动图演示) 对各种排序算法有详细的解释。根据快排的思路,我们先选择数组中一个随机的数字,然后调整数组中数字顺序,使得左边小,右边大。如果这个数字的下标正好是n/2,那么得到我们的结果
- 如果下标大于n/2,那么中位数在 左边,则同样的算法用在左边的数列上
- 如果下标小于n/2,那么中位数在右边,则同样的算法用在作右边的数列上
- 典型的递归
-
经过如上分析有如下代码:
/**
* 如果数组中超过数组一半的数都是同一个数,找出那个数字
* @author liaojiamin
* @Date:Created in 17:21 2021/5/27
*/
public class MoreThenHalfNum {
public static void main(String[] args) {
int[] arrayData = {4,4,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6};
System.out.println(findMiddleNum(arrayData, 0, arrayData.length -1));
}
/**
* 方法一:二分查找思想找出 数组中间大小的那个数字
* 如果该数字在数组中的个数超过数组的一半,那么中间大小的数字必然是这个数字
* */
public static int findMiddleNum(int[] arrayData, int left, int right){
if(arrayData == null || arrayData.length <= 1){
return -1;
}
Integer middle = (left + right)/2;
if(left < right){
int index = swapArray(arrayData, left, right);
if(index == middle){
return arrayData[middle];
}
quickSort(arrayData, left, index - 1);
quickSort(arrayData, index + 1, right);
}
return arrayData[middle];
}
/**
* 挖坑法二分查找
* */
public static int swapArray(int[] arrayData, Integer left, Integer right){
if(left < right){
int position = arrayData[left];
while (left < right){
while (right > left && arrayData[right] > position){
right --;
}
if(left < right){
arrayData[left] = arrayData[right];
left ++;
}
while (left < right && arrayData[left] < position){
left ++;
}
if(left < right){
arrayData[right] = arrayData[left];
right--;
}
}
arrayData[left] = position;
}
return left;
}
}
-
以上解法时间复杂度能达到O(n),但是还是有点复杂的,可能你写不出来,是否有更简单的方法?
-
解法三:依然分析数组的特性,超过一半的存有量,也就是他比他的数字出现的次数的总和还要多,
- 那么我们只需要统计个数就能得到了
- 只进行一次遍历数组,保存两个值,一个数组中数字,一个他的次数
- 如果遍历到下一个数据的时候,还是这个数,那么次数加 +1,否则次数 -1
- 如果次数达到0 ,那么情况保存的这个数,在接着遍历依然用以上逻辑
- 由于我们要找的数据出现的次数比其他数字总和还要多,那么肯定最后一个保存下来的数据必然是我们要的那个出现此处过半的数据。
-
根据如上分析有如下代码:
/**
* 如果数组中超过数组一半的数都是同一个数,找出那个数字
* @author liaojiamin
* @Date:Created in 17:21 2021/5/27
*/
public class MoreThenHalfNum {
public static void main(String[] args) {
int[] arrayData = {4,4,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6};
System.out.println(findThenHalfNum(arrayData));
}
/**
* 方法二:依次遍历数组中每一个数字,将遇到的数字保存到 k 对象中,如果依次遇到的数字中与k中相同统计+1
* 否则 -1,如果统计为0 情况k对象,依次对每个数字操作,最终保留下来的比如是数量最多的那个数字
* */
public static int findThenHalfNum(int[] arrayData){
if(arrayData == null || arrayData.length <= 1){
return -1;
}
int findNum = -1;
int findNumCount = -1;
for (int i = 0; i < arrayData.length; i++) {
if(findNum == -1){
findNum = arrayData[i];
findNumCount = 1;
}else {
if(findNumCount == 0){
findNum = arrayData[i];
findNumCount =1;
}else {
if(findNum == arrayData[i]){
findNumCount ++;
}else {
findNumCount --;
}
}
}
}
return findNum;
}
}
- 如上第二第三都是最优解,但是第二中修改了数组,第三种没有修改数组,可以依据具体情况来决定方案。