Day 1

字符串转为整数 (atoi)

2021.11.18

问题描述

问题是面试官描述的,如下:

接受输入一串数字(可带正负号),将其转换为int类型并输出,不能使用atoi函数等。面试官说不用考虑用户输入非法。

我的思路

面试的时候,想到的是把字符串转换成char数组,数的位置序号正好和数组位置是对应的,可以按照权值展开来计算。并不是优解,因为转换为了char数组,使用了辅助空间,空间复杂度变大。

要点归纳

  1. 算数;
  2. 有无符号;
  3. 考虑int溢出;

渣代码

public class autoInt {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String input = in.next();
        System.out.println(autoInt(input));
    }
    public static int autoInt(String str){
        int isSign =0;
        int isNag = 0;
        if(str == null) return 0;
        if(str=="-2147483648")  return -2147483648;
        if(str.charAt(0)=='+'||str.charAt(0)=='-'){
            isSign=1;
            if(str.charAt(0)=='-')  isNag=1;
        }
        //char[] st = str.toCharArray();

        int result = 0;
        for (int i = 0+isSign;i<str.length();i++){
            result =result*10+(str.charAt(i)-'0');
            if(result<0){
                System.out.println("overflow");
                return 0;
            }
        }
        if(isNag==1){
            return -result;
        }
        return result;
    }
}

Day 2

第一个错误版本

问题描述

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

我的思路+解法

递归解决:用二分每次把全对或者全错的部分截断,直到只剩下一对一错,返回后一个结果;

效果:用时击败38.95%;内存消耗击败11.49%;菜啊

结论:应该是想麻烦了,而且使用递归会导致更多的资源消耗;

//每次把全对或者全错都截断;

public class Solution extends VersionControl {
    public int firstBadVersion(int n){
        if(isBadVersion(1))
            return 1;
        return Cut(1,n);
    }

    public int Cut(int start,int end){
        if(end-start==1)
            return end;
        long secure = ((long)start+end)/2;
        int mid =(int)secure;

        //中间为坏,截去后半截
        if(isBadVersion(mid)){
            return Cut(start,mid);
        }
        //中间为好,截去前半截
        else{
            return Cut(mid,end);               
        }  
    }
}

更好的解:

优化的地方:

  1. 别转换成long类型,换种方法来解决溢出问题: mid = low+(high-low)/2;

  2. 不用递归,而是直接用while

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int low = 1;
        int high=n;
        int mid =1;
        while(low<high){

            mid = low+(high-low)/2;

            if(isBadVersion(mid)){
                high = mid;
            }else{
                low = mid+1;
            }
        }
        return high;
    }
}

Day 3

有序数组的平方

题目描述

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

我的思路

找到正负数的分界线,然后用两个游标,负数往左滑,正数往右滑,比较大小。当正数或者负数一方耗尽时,直接全部把剩余的数接上;

要注意的点:全正或者全负的时候;0算正数还是负数;

渣代码

//总感觉全正数的时候更优化,但是没找到更优雅的解法
class Solution  {
    public int[] sortedSquares(int[] nums) {
        //找分界线;
        int divide = 0;
        //全正
        if(nums[0]>=0)  divide=0;
        //全负;
        else if(nums[nums.length-1]<=0)  divide=nums.length-1;
        else{
            for(int i = 0;i<nums.length-1;i++){
                if((nums[i]*nums[i+1]<=0))  {
                    divide=i;
                    break;
                    }
            }   
        }
        //正数指针
        int i = divide+1;
        //负数指针
        int j = divide;
        //写头
        int index = 0;

        int[] result = new int[nums.length];

        //while这里有问题,比如全正全负
        while((i<=nums.length-1) && (j)>=0){
            if(nums[i]*nums[i]<=nums[j]*nums[j]){
                result[index]=nums[i]*nums[i];
                i++;
            }else{
                result[index]=nums[j]*nums[j];
                j--;
            }
            index++;
        }

        //剩下的
        while(i<=nums.length-1){
            result[index]=nums[i]*nums[i];
            i++;
            index++;
        }

        while(j>=0){
            result[index]=nums[j]*nums[j];
            j--;
            index++;
        }
        return result;
    }
}

另一种解法(leetcode上的)

思路:不用考虑正负,直接从数组两端向中间读数比较;

我的思考:这样会不会多余了比较次数,比如全正全负时,就需要比较length次;而找到正负分界点做的指针的比较次数在大多情况下都更少;

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        for (int i = 0, j = n - 1, pos = n - 1; i <= j;) {
            if (nums[i] * nums[i] > nums[j] * nums[j]) {
                ans[pos] = nums[i] * nums[i];
                ++i;
            } else {
                ans[pos] = nums[j] * nums[j];
                --j;
            }
            --pos;
        }
        return ans;
    }
}

轮转数组

解法一

直接每个元素右移动k次--BiBi超出时间限制了

class Solution {
    public void rotate(int[] nums, int k) {
        for(int i=1;i<=k;i++){
            int temp = nums[nums.length-1];
            for(int j = nums.length-1 ;j>0;j--){
                nums[j]=nums[j-1];
            }
            nums[0]=temp;
        }
    }
}

解法二

也是简单粗暴,建立一个等长的辅助数组,把元素组0开始,length-k个元素拷贝到新数组的从k到末尾;再拷贝元素组的最后k个到新数组前面,最后把辅助数组的内容贴过去。

注意的点:数组拷贝时要拷贝的东西;考虑当移动次数大于数组元素个数时,取k=k%len;

效果:用时:击败了63.31%,内存消耗:击败了88.03%

    public void rotate(int[] nums, int k) {
        if(k>nums.length){
            k=k%nums.length;
        }
        int[] helper =new int[nums.length];
        System.arraycopy(nums,0,helper,k,nums.length-k);       
        for(int i =0;i<k;i++){
            helper[i]=nums[nums.length-k+i];
        }
        for(int i =0;i<nums.length;i++)
            nums[i]=helper[i];
    }
}

折腾很久的解法三

思路:为了防止覆盖,把数组拆成(len/k)个长度为k的组+剩余部分(len%k);形如BA1A2A3A4 B A1 A2 A3 A4,移动后为A4 BA1AA2A3A4~B A1 AA2 A3;将i,i+k,i+2ki,i+k,i+2k为一组来移动;

疑惑:用的数组长度为k,且k=k%len,辅助数组的长度小于解法二的len长数组,为什么内存消耗反而变大了。

效果:64%;65%

class Solution {
    //更直接的方法,新建一个等长的辅助数组
    public void rotate(int[] nums, int k) {
        // //辅助数组,存放i,i+k,i+2k....
        if(k>nums.length)   k=k%nums.length;
        if(k==0)    return;
        int times = nums.length / k;

        int[] overflow =(int[])Arrays.copyOfRange(nums,nums.length-k,nums.length);
        //开始分组复制、
        for(int i =0;i<k;i++){
            for(int n = times;n>0;n--){
                if(i+n*k<nums.length)
                    nums[i+n*k]=nums[i+(n-1)*k];
                }
            }
        for(int i=0;i<overflow.length;i++){
            nums[i]=overflow[i];
        }
}
//???为什么啊,解法二用了一个等长数组,解法算只用了长度为一定小于len的数组,,,为什么内存消耗变大了
}

完全没想到的解法:翻转数组

但是还是不知道原理,禁止在麻瓜面前使用魔法;

从leetcode找的解:

  1. 先整个把数组反转一下
  2. 把前半段反转
  3. 把后半段反转

举例来说:

nums = [1,2,3,4,5,6,7], k = 3

第一步,整个数组反转:7,6,5,4,3,2,1

第二步,把前半段反转:5,6,7,4,3,2,1

第三步,把后半段反转:5,6,7,1,2,3,4

作者:angela-x

class Solution {
    public void rotate(int[] nums, int k) {
        k %= nums.length;
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }

    public void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start += 1;
            end -= 1;
        }
    }
}

Day 4

移动0

解法一

思路

一边统计0一边移动,每个元素移动前面的0个数次。

渣代码
class Solution {
    public void moveZeroes(int[] nums) {
        int movetimes = 0;
        for(int i = 0;i<nums.length;i++){
            if(nums[i]==0)  movetimes++;
            else if(movetimes!=0)   move(nums,i,movetimes);
        }
        for(int i = 1;i<=movetimes;i++) nums[nums.length-i]=0;
    }
    public void move(int[] nums,int index,int times){
        nums[index-times]=nums[index];
    }
}
效果

时间:41.82% 内存:51.84%

更好的解法:双指针

思路

其实和我的解法差不多?但是用指针解放了计数要求。我的解法是指明向前移动多少格,双指针用一个指针来直接说明移动到哪。

效果

时间:65% 内存:35%

代码
class Solution {
    public void moveZeroes(int[] nums) {
        int index = 0;
        for(int n : nums){
            if(n!=0){
                nums[index]=n;
                index++;
            }
        }
        for(int i = index;i<nums.length;i++){
            nums[i]=0;
        }
    }
}

一次遍历的双指针

思路

要一次遍历完成,就是优化掉放0的过程,把最后的置0变成交换; 上一版的双指针,把0值覆盖,这一步的双指针把0交换;

效果

时间:42% 内存:8%

代码
class Solution {
	public void moveZeroes(int[] nums) {
		if(nums==null) {
			return;
		}
		//两个指针i和j
		int j = 0;
		for(int i=0;i<nums.length;i++) {
			//当前元素!=0,就把其交换到左边,等于0的交换到右边
			if(nums[i]!=0) {
				int tmp = nums[i];
				nums[i] = nums[j];
				nums[j++] = tmp;
			}
		}
	}
}

今天就到这,上午电脑坏了拿去修了,痛失260。5555

Day 5

题目一

题目描述

解法一

思路

一开始想的类似于二叉树剪枝,但是没有现成的树结构可以用。因为不用判断剪枝条件,所以直接改思路得到二叉树的输出结果:nums元素长度的01序列,序列的位数与nums元素对应,1代表有该元素,0代表没有。由于没有剪枝条件,直接for循环i++生成即可。

注意:
  1. 又忘记了次方的函数Math.pow(a,b);返回double。
  2. 得到二进制的某一位: 方法一:(num>>位数)&1;

方法二:(num >> (bit -1)) & 1;

效果

时间:100% 空间:94%

渣代码
class Solution {
    //从0开始,按照位+1;
    public List<List<Integer>> subsets(int[] nums) {
        int set =(int)Math.pow(2,nums.length);
        ArrayList<List<Integer>> result= new ArrayList<>();
        for(int i=0;i<set;i++){
            ArrayList<Integer> temp = new ArrayList<>();
            for(int j = 0;j<nums.length;j++){
                if(((i>>j)&1)==1){
                    temp.add(nums[j]);
                }
            }
            result.add(temp);
        }
        return result;
    }
}

没想到的解法二:递归

思路

从空集开始,递归加入每个元素到已有的每一个集合;

代码
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result= new ArrayList<>();
        //加入空集;
        result.add(new ArrayList<Integer>());
        
        for(int num:nums){
            List<List<Integer>> newSubsets = new ArrayList<>();
            for(List<Integer> subset :result){
                ArrayList<Integer> newSubset = new ArrayList<>(subset);
                newSubset.add(num);
                newSubsets.add(newSubset);
            }
            result.addAll(newSubsets);
        }
        return result;
    }
}

题目二:

描述

类似压缩机制,输入 a2b3c4d 输出 abbcccdddd;输入数字个字母。破防题

渣代码

public class Solution {
    public static void main(String[] args) {
        String input ="a2b3c11d";
        System.out.println(getSolution(input));
    }

    public static String getSolution(String str){
        String result ="";
        for(int i =0;i<str.length();i++){
            char letter = str.charAt(i);
            //字母
            if(str.charAt(i)-'0'>9){
                result += letter;
            }
            //是数字
            else{
                int j = i;
                while (str.charAt(++j)-'0'<10);
                letter=str.charAt(j);
                int times = Integer.valueOf(str.substring(i,j));
                while(times-->0){
                    result +=letter;
                }
                i=j;
            }
        }
        return result;
    }
}

题目三 两数之和

题目描述

太长了点连接吧

我的解法

思路

逐个取数,算target-num,向后二分查找是否有答案;

渣代码
class Solution {
    //逐个向右,二分查找
    public int[] twoSum(int[] numbers, int target) {
        int[] result = new int[2];
        //题目暗示一定有答案,不用判断是否有可能没答案;
        for(int i =0;i<numbers.length;i++){
            int tar = target-numbers[i];
            //本来想提早过滤,但是没有考虑负数+0;
            // if(tar>(numbers[i]+numbers[numbers.length-1]))  
            //     continue;
            int index = BiSearch(numbers,i+1,tar);
            if(index!=-1){
                result[0] = i+1;
                result[1] = index+1;
                break;
            }
        }
        return result;
    }

    //test过了,能用
    public static int BiSearch(int[] nums,int start,int tar){
        int left = start;
        int right = nums.length;
        while(left<=right){
            int mid = left+(right-left)/2;
            if(mid>nums.length-1)    break;
            if(nums[mid]==tar)  return mid;
            else if(nums[mid]>tar)  right=mid-1;
            else if(nums[mid]<tar)  left=mid+1;
        }
        //失败
        return -1;
    }
}

没想到的解:双指针