算法准备-4.15
1. 数组中超过一半的数字(摩尔投票法)
-
描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字
-
未解思路:此题采用分治算法,在原数组中超过一半的数字,在等分的左右两数组中至少有一个中也超过了一半,而且因为是超过一半,所以两个等分数组中都存在此元素
-
新学思路:摩尔投票法:摩尔投票法基于当一个数的重复次数超过数组长度的一半,每次将两个不相同的元素删除,最终剩下的就是要找的数。用votes表示票数,初始化为0,当votes为0时,将当前元素设为众数,遍历数组,与其相等的,votes加一,否则减一。
-
题解:
class Solution { public int majorityElement(int[] nums) { int x=0; int votes=0; for(int i=0;i<nums.length;i++) { if(votes==0) { x=nums[i]; } votes+=x==nums[i]?1:-1; } return x; } }
-
扩展:此题还有一种比较常见的解法就是先对数组进行排序,得到新数组的中位数即为众数
class Solution { public int majorityElement(int[] nums) { sort(nums); int mid=(nums.length-1)/2; return nums[mid]; } public void sort(int[] nums){ for(int i=0;i<nums.length;i++) { for(int j=0;j<nums.length-i-1;j++) { if(nums[j+1]<nums[j]) { int temp=nums[j]; nums[j]=nums[j+1]; nums[j+1]=temp; } } } } }
2. 最小的k个数
-
描述:输入n个整数,找出其中最小的k个数
-
思路1:比较普通,就手撕一遍快速排序,然后选择前k个数字就可以了
-
题解1:
class Solution { public int[] getLeastNumbers(int[] arr, int k) { quicksort(arr,0,arr.length-1); return Arrays.copyOf(arr,k); } public void quicksort(int[] arr,int low,int high){ if(low>high) return; int i,j,temp; i=low; j=high; temp=arr[low]; while(i<j) { while(temp<=arr[j]&&i<j) { j--; } while(temp>=arr[i]&&i<j) { i++; } if(i<j) { int m=arr[i]; arr[i]=arr[j]; arr[j]=m; } } arr[low]=arr[i]; arr[i]=temp; quicksort(arr,low,i-1); quicksort(arr,i+1,high); } }
优化:此题可以借鉴快排的划分思想,做快速选择,在每次切分的时候只处理一边,比较返回的位数与k-1的大小,区别就在于最后一部分
class Solution { public int[] getLeastNumbers(int[] arr, int k) { quicksort(arr,0,arr.length-1,k); return Arrays.copyOf(arr,k); } public void quicksort(int[] arr,int low,int high,int k){ if(low>high) return; int i,j,temp; i=low; j=high; temp=arr[low]; while(i<j) { while(temp<=arr[j]&&i<j) { j--; } while(temp>=arr[i]&&i<j) { i++; } if(i<j) { int m=arr[i]; arr[i]=arr[j]; arr[j]=m; } } arr[low]=arr[i]; arr[i]=temp; if(i==k-1) { return; } if(i<k-1) { quicksort(arr,i+1,high,k); } else { quicksort(arr,low,i-1,k); } } }
3. 连续子数组的最大和
-
描述:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
-
思路:这是分治算法的典型,最大连续子数组有三种情况,存在于其左半区间,存在于其右半区间,两半区间都有,分别找出三种情况中的最大值在进行比较即可
-
题解:
class Solution { int MIN=-100000; public int maxSubArray(int[] nums) { return findarray(nums,0,nums.length-1); } public int findarray(int[] nums,int low,int high){//递归寻找左右两端的最大子数组 if(low==high) return nums[low]; int mid=(low+high)/2; int leftsum=findarray(nums,low,mid); int rightsum=findarray(nums,mid+1,high); int crosssum=findcrossarray(nums,low,high); return max(leftsum,rightsum,crosssum); } public int findcrossarray(int[] nums,int low,int high){//寻找跨越中点的最大子数组 int leftsum=MIN; int rightsum=MIN; int sum1=0; int sum2=0; int mid=(low+high)/2; for(int i=mid;i>=low;i--)//找出左端的最大值 { sum1+=nums[i]; if(sum1>leftsum) { leftsum=sum1; } } for(int j=mid+1;j<=high;j++)//找出右端的最大值 { sum2+=nums[j]; if(sum2>rightsum) { rightsum=sum2; } } return leftsum+rightsum; } public int max(int a,int b,int c){ if(a>=b&&a>=c) { return a; } if(b>=a&&b>=c) { return b; } return c; } }
但是这题分治算法的时间复杂度超出了题目的要求,分治算法的时间复杂度为O(nlgn),下面的动态规划是我参考评论区大佬的算法
-
动态规划思路:设动态规划列表dp,dp[i]代表以元素nums[i]为结尾的连续子数组最大和;转移时:若dp[i-1]<=0,说明dp[i-1]对dp[i]产生了负贡献,即dp[i-1]+nums[i]还不如nums[i]本身
-
动态规划题解:
class Solution { public int maxSubArray(int[] nums) { int res = nums[0]; for(int i = 1; i < nums.length; i++) { nums[i] += Math.max(nums[i - 1], 0); res = Math.max(res, nums[i]); } return res; } }