- 计算机语言和开发平台日新月异,但万变不离其宗的是那些算法和理论。
- 算法和理论永远是程序员最重要的内功,秃头也得学呀
记录部分 当时的思路或者学习其他优秀作者的思路,也帮自己重新梳理逻辑,争取加深印象,加油 (^-^)V
下一个排序
思路:从右往左,找到一个比他前面大的数,比如1,3,2。我们找到这个数3符合要求。
此时意味着:3之后的序列包括3都是最大的数了。
在以上例子中:3前面还有1,我们在3之后的序列包括3中挑选一个大于1的最小数,和1调换位置
如果3之前没有数,那么说明这个数已经是最大序列,只需直接将他升序排序。
class Solution {
public void nextPermutation(int[] nums) {
if(nums.length<=1) return;
//找到比上一个数大的数
int i ;
for ( i = nums.length-1; i > 0; i--) {
//寻找一个数,使得比他前面的数大。如果没有则这个序列已经是最大序列
if(nums[i] > nums[i-1]){
int index = i;
for (int j = i; j < nums.length; j++) {
if(nums[j]>nums[i-1] && nums[j]<nums[index]){
index = j;
}
}
int t = nums[index];
nums[index] = nums[i-1];
nums[i-1] = t;
break;
}
}
Arrays.sort(nums,i,nums.length);
}
}
总持续时间可被60整除的歌曲
刚遇到这道题,给我的第一感觉:太简单了吧!
对的,如果要做出来结果来是非常的简单,你可以用暴力双重循环列出所有2个数的组合和,然后对60取模。结果就是:
这是一道数组题,时刻提醒我们运用数组解决问题
暴力法的时间复杂度为O(n2),暴力法的时间花在越往后的数字,重复取模的次数会越来越多,因为所有数的取模结果只有60种,可以考虑用一个长度60的数组存储所有取模结果再计算结果,时间复杂度 为O(n)。
- 我先想到的是。从数组尾巴
n=time[tail]%60
出发,每次对count+=res[60-n]
计算,就是算法1,但是结果不是特别令人满意,3ms
,击败60%,于是参考别人的代码; - 优化:题目给了一个例子[60,60,60],他们取模都是0,且也需要取模为0的数配对。假设n个数都是取模为0或30的数,那他们对count操作次数就是n-1次。
如果一次知道取模为0的个数为(假设)i,那么他们配对个数就是i*(i-1)/2。只需要计算一次。其他情况通用适用,于是有解法2。
//解法1
public int numPairsDivisibleBy60(int[] time) {
int[]res=new int[60];
int count=0;
for (int i = time.length-1; i >=0; i--) {
int left=time[i]%60;
if(left!=0)
count+=res[60-left];
else count+=res[0];
res[left]++;
}
return count;
}
//解法2
public int numPairsDivisibleBy60(int[] time) {
int[]res=new int[60];
int count=0;
for(int i:time){
res[i%60]++;
}
count+=(res[0]*(res[0]-1))/2;
count+=(res[30]*(res[30]-1))/2;
int i=1,j=59;
while (i<j){
count+=res[i++]*res[j--];
}
return count;
}
滑动窗口中位数
分析 根据题意,要找出滑动窗口中位数,我们可以令窗口中的部分数据有序,得到中位数,主要难点就是避免频繁的对窗口数据进行排序。
解题 每次窗口滑动一格,就会有一个数据排除,一个数据进入窗口,可以利用二分算法
先找到被排除数据,再用快速排序
思想快速定位进入窗口的数。
public static double[] medianSlidingWindow(int[] nums, int k) {
//存储结果
double res[] = new double[nums.length - k + 1];
//储存窗口数据
int win[] = new int[k];
for (int i = 0; i < k; i++) {
win[i] = nums[i];
}
Arrays.sort(win);
//第一个中位数
res[0] = getCenter(win);
//利用二分算法和快速排序维护滑动窗口
for (int i = k; i < nums.length; i++) {
int target = nums[i-k];
int index = getTargetIndex(win,target,0,k-1);
while (index > 0 && win[index-1]>nums[i]){
win[index] = win[index-1];
index--;
}
while (index < k-1 && win[index+1]<nums[i]){
win[index] = win[index+1];
index++;
}
win[index] = nums[i];
res[i-k+1] = getCenter(win);
}
return res;
}
//获取中位数
public static double getCenter(int[] nums){
int center = (nums.length-1)/2;
return nums.length%2==0 ? (nums[center+1]/2.0 + nums[center]/2.0) : nums[center];
}
//二分算法找到目标数据下标
public static int getTargetIndex(int[]win,int target,int i,int j){
int index = (i+j)/2;
if (win[index]==target){
return index;
}
if(win[index] > target){
return getTargetIndex(win,target,i,index-1);
}else {
return getTargetIndex(win,target,index+1,j);
}
}
盛水最多的容器
题意已经很明确了,就是找到两个柱子和X轴围成最大的面积。
第一思路肯定是蛮力法穷举所有可能的面积取最大值,思考如何改进。
变量: 设置指针i,j
分别从左右端开始扫描,高度分别为l,r
,最大面积max为(j-i)*min(l,r)
思考: 如果数组的值(柱高)是随机分布,那么从数组两端开始更可能先获取最大面积,之后过滤不可能的值。
步骤:
//开始
i=0 ; j=8;
l=height[i] ; r=height[j];
max=1*8=8;
//选择柱子低的一方指针,往中间移动,直到height[i]>l
l=height[1]=8
//计算面积是否更大
7*7=49>max;
max=49
//现在是r<l了,右边指针往中间移动,直到j=6
8*6<49
继续移动
index=2,3,4,5,的柱子都比 左边最高的l 或者右边最高的r 短,忽略,从而得到最大值49,省略了不必要的计算。
public int maxArea(int[] height) {
int i=0,j=height.length-1,l=0,r=0,max=0;
while(i<j){
l=height[i];
r=height[j];
if(l<=r){
max=Math.max(max,(j-i)*l);
while (i<height.length&&height[i]<=l)i++;
}else {
max=Math.max(max,(j-i)*r);
while (j>=0&&height[j]<=r)j--;
}
}
return max;
}
数组中出现超过一半的数
按照一打一的思想,如果一个数n大于总数的一半,一对一消掉后,肯定还剩至少一个n。
为了证明n出现次数大于一半,在进行一轮循环计算n的个数。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int cur = array[0];
int times = 1;
for(int i = 1 ;i < array.length ; i++){
if(cur == array[i]){
times++;
}else{
times -- ;
}
if(times == 0){
cur = array[i];
times = 1;
}
}
times = 0;
for(int i = 0;i<array.length;i++){
if(array[i] == cur){
times ++ ;
}
}
if(times>array.length/2){
return cur;
}
return 0;
}
}
最长无重复子串(int)
问题可以转换成如何根据数据快速获取它的下标。
我们可以想到hashmap,和数组。
此处不建议使用HashMap,n的长度已经限定在10万,可以采用数组直接计算物理位置的特性来快速获取目标数下标,而hashmap需要通过hashcode()、链表、红黑树 等路径去寻找目标数,才能获取下标。
public int maxLength (int[] arr) {
// write code here
int[] lastPos = new int[100005];
int len = arr.length;
int start = 0;
int res = 0;
for(int i =0;i<len;i++){
int now = arr[i];
start = Math.max(start,lastPos[now]);
res = Math.max(res,i-start+1);
lastPos[now] = i+1;
}
return res;
}
最大子数组累加和问题
问题切入点:如果前人留下了正资产,我继承,否则我不继承。
//分别存储 当前最大值`cur`和最大值`max`
public int maxsumofSubarray (int[] arr) {
// write code here
int max = 0,cur = 0;
for(int i= 0; i<arr.length;i++){
cur+=arr[i];
if(cur < 0){
cur = 0;
}else if(cur > max){
max = cur;
}
}
return max;
}
第K大的数
快排改造:
public int findKth(int[] a, int n, int K) {
// write code here
quickSort(a,K,0,n-1);
return a[n-K];
}
public void quickSort(int[] a,int k , int l ,int r){
if(l>=r)return;
int t = a[l];
int i = l, j = r;
while(i<j){
while(i<j && a[j]>=t)j--;
a[i] = a[j];
while(i<j && a[i]<=t)i++;
a[j] = a[i];
}
a[i] = t;
if(i==a.length-k)return;
if(i>a.length-k)
quickSort(a,k,l,i);
else
quickSort(a,k,i+1,r);
}
最小的K个数
问题切入点:选择排序
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
int f=0;
ArrayList<Integer> s= new ArrayList<Integer>();
if (k>input.length)
return s;
for (int i =0 ; i<k ; i++) {
for (int j=i;j<input.length;j++) {
if (input[i]> input[j])
{
f=input[i];
input[i]=input[j];
input[j]=f;
}
}
s.add(input[i]);
}
return s;
}
排序
这里使用快速排序,平均时间复杂度为n log n
public int[] MySort (int[] arr) {
if(arr.length<=1){
return arr;
}
quick(arr,0,arr.length-1);
return arr;
}
public void quick(int [] args,int n,int m ){
if(n>=m)return;
int base=args[n],i=n,j=m;
while (i<j){
while (i<j && args[j]>=base){
j--;
}
args[i]=args[j];
while (i<j && args[i]<=base){
i++;
}
args[j]=args[i];
}
args[i]=base;
quick(args,n,i-1);
quick(args,i+1,m);
}