力扣刷题入坑(JAVA版)


一、20天「算法」刷题计划

   传送门:算法入门

1、二分查找

(1)二分查找(704)★

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target ,如果目标值存在返回下标,否则返回 -1。

示例 1:
  输入: nums = [-1,0,3,5,9,12], target = 9
  输出: 4
  解释: 9 出现在 nums 中并且下标为4

示例 2:

  输入: nums = [-1,0,3,5,9,12], target = 2
  输出: -1
  解释: 2 不存在 nums 中因此返回

提示:

  1. 你可以假设nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000] 之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

官方解法:

在这里插入图片描述

class Solution {
    public int search(int[] nums, int target) {      // nums是int型数组,target是int型变量
        int low = 0, high = nums.length - 1;         // low是left位置,high是right位置
        while (low <= high) {
            int mid = (high - low) / 2 + low;        // mid[left,right]的中间位置
            int num = nums[mid];                     // num是[left,right]区间的中间值
            if (num == target) {                     // 中间值恰好是目标值
                return mid;                          // 结束查找
            } 
            else if (num > target) {                 // 中间值比目标值大
                high = mid - 1;                      // [left,right]缩小一半(取前半段)
            } 
            else {                                   // 中间值比目标值大
                low = mid + 1;                       // [left,right]缩小一半(取后半段)
            }
        }
        return -1;
    }
}

复杂度分析

  • 时间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n是数组的长度。
  • 空间复杂度: O ( 1 ) O(1) O(1)

注意:

  • 循环条件是 low <= high,==时也有意义(未查找到)
  • mid不用(low+high)/2,防止low+high溢出
  • 当缩小查找范围时,不是直接用mid做边界,而是需要+/-1

(2)第一个错误的版本(278)★

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

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

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

示例 1

  输入:n = 5, bad = 4
  输出:4
  解释:
    调用 isBadVersion(3) -> false
    调用 isBadVersion(5) -> true
    调用 isBadVersion(4) -> true
    所以,4 是第一个错误的版本。

示例2

  输入:n = 1, bad = 1
  输出:1

提示:
  1 <= bad <= n <= 2 31 2^{31} 231 - 1

官方解法:

在这里插入图片描述

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1, right = n;
        while (left < right) {                        // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2;      // 防止计算时溢出
            if (isBadVersion(mid)) {
                right = mid;                          // 答案在区间 [left, mid] 中
            } else {
                left = mid + 1;                       // 答案在区间 [mid+1, right] 中
            }
        }
        return left;                                  // 当left = right时为答案
    }
}

复杂度分析

  • 时间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n是给定版本的数量。

  • 空间复杂度: O ( 1 ) O(1) O(1)。我们只需要常数的空间保存若干变量。

注意:

  • 该问题是找左右端点相同时的点。循环条件不包括相等的情况。

(3)搜索插入位置(35)★

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的算法。

示例 1:
  输入: nums = [1,3,5,6], target = 5
  输出: 2

示例 2:
  输入: nums = [1,3,5,6], target = 2
  输出: 1

示例 3:
  输入: nums = [1,3,5,6], target = 7
  输出: 4

示例 4:
  输入: nums = [1,3,5,6], target = 0
  输出: 0

示例 5:
  输入: nums = [1], target = 0
  输出: 0

提示:

  •   1 <= nums.length <= 1 0 4 10^4 104
  •   - 1 0 4 10^4 104 <= nums[i] <= 1 0 4 10^4 104
  •   nums 为无重复元素的升序排列数组
  •   - 1 0 4 10^4 104 <= target <= 1 0 4 10^4 104

官方解法:

在这里插入图片描述

class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;
        int left = 0, right = n - 1, ans = n;
        while (left <= right) {
            int mid = ((right - left) >> 1) + left;
            if (target <= nums[mid]) {
                ans = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n为数组的长度。二分查找所需的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

  • 空间复杂度: O ( 1 ) O(1) O(1)。我们只需要常数空间存放若干变量。

注意:

  • 注意target <= nums[mid]条件下才给ans赋值mid。
  • 本来想的用flag法(target<nums[mid]和target>nums[mid]时给flag赋不同的值,再循环外根据flag值返回不同值)不对

2、双指针

(1)有序数组的平方(977)★

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

示例 1:
  输入:nums = [-4,-1,0,3,10]
  输出:[0,1,9,16,100]
  解释:平方后,数组变为 [16,1,0,9,100];排序后,数组变为 [0,1,9,16,100]

示例 2:
  输入:nums = [-7,-3,2,3,11]
  输出:[4,9,9,49,121]

提示:

  • 1 <= nums.length <= 1 0 4 10^4 104
  • - 1 0 4 10^4 104 <= nums[i] <= 1 0 4 10^4 104
  • nums 已按非递减顺序排序


进阶:
  请你设计时间复杂度为 O(n) 的算法解决本问题

官方解法:

  1. 最简单粗暴的方法是循环计算平方数后,调用Arrays.sort()函数进行排序。
class Solution {
    public int[] sortedSquares(int[] nums) {
        int[] ans = new int[nums.length];            // 新建数组
        for (int i = 0; i < nums.length; ++i) {
            ans[i] = nums[i] * nums[i];
        }
        Arrays.sort(ans);
        return ans;
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n 是数组 nums 的长度。
  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn)。除了存储答案的数组以外,我们需要 O ( log ⁡ n ) O(\log n) O(logn)的栈空间进行排序。

补充:

  • Java新建数组方法:
    - int[] arr = new int[10];   int[][] arr = new int[m][n];
    - int[] arr = {10,20,30};   int[][] arr = {{1,2,3},{4,5,6},{7,8,9}};
    - int[] arr = new int[]{10,20,30};   int[][] arr = new int[][]{{1,2,3},{4,5,6},{7,8,9}};
  1. 双指针法1

在这里插入图片描述

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int negative = -1;
        for (int i = 0; i < n; ++i) {
            if (nums[i] < 0) {
                negative = i;            // 找到正负数分界线(最大的那个负数的位置)
            } else {
                break;
            }
        }
        int[] ans = new int[n];
        int index = 0, i = negative, j = negative + 1;   				// index是最终的索引,i是最大负数的位置,j是最小非负数的位置
        while (i >= 0 || j < n) {                        				// i>=0表示有负数,j<n表示有正数。(这条件其实包含了所有可能)
            // -------------- 如果全是正数(每次循环进这个)------------
            if (i < 0) {								 			
                ans[index] = nums[j] * nums[j];			 			 
                ++j;
            } 
            // -------------- 如果全是负数(每次循环进这个)------------
            else if (j == n) {						   			
                ans[index] = nums[i] * nums[i];
                --i;
            } 
            // ----- 有正数有负数(每次循环根据条件进入下两个中的一个)----- 
            else if (nums[i] * nums[i] < nums[j] * nums[j]) {			// 最大负数的平方<最小正数的平方
                ans[index] = nums[i] * nums[i];
                --i;
            } else {													// 最大负数的平方>=最小正数
                ans[index] = nums[j] * nums[j];
                ++j;
            }
            ++index;
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n是数组nums 的长度。
  • 空间复杂度: O ( 1 ) O(1) O(1)。除了存储答案的数组以外,我们只需要维护常量空间。
  1. 双指针法2

在这里插入图片描述

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;												// 最终生成数的指针前移(ans[]是从最大数开始,向前逐个生成的)
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n是数组nums 的长度。
  • 空间复杂度: O ( 1 ) O(1) O(1)。除了存储答案的数组以外,我们只需要维护常量空间。

同理还可以用首位数添负号与末尾数比较(好像更简单)

class Solution {
    public int[] sortedSquares(int[] nums) {
        int length = nums.length;            						// 获得数组长度
        int[] out = new int[length];     							// 定义输出数组
        int left = 0, right = length - 1, outpoint = length - 1;    // 左指针为0,右指针在末尾,输出指针在末尾
        while(left<=right){       									// 循环条件:左指针 <= 右指针
            if(-nums[left] >= nums[right]){    						// 如果左数绝对值>=右数
                out[outpoint] = nums[left] * nums[left];    		// 输出是左数平方
                left++;     										// 左指针右移
            }else{                                  				// 如果左数绝对值<右数
                out[outpoint] = nums[right] * nums[right];    		// 输出是右数平方
                right--;     										// 右指针左移
            }
            outpoint--;     										// 输出指针前移
        }
        return out;
    }
}

注意: 循环条件包含“=”,否则会缺少最小的那个数,即out[0]

(2)轮转数组(189)★★

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:
  输入: nums = [1,2,3,4,5,6,7], k = 3
  输出: [5,6,7,1,2,3,4]
  解释:
    向右轮转 1 步: [7,1,2,3,4,5,6]
    向右轮转 2 步: [6,7,1,2,3,4,5]
    向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:
  输入:nums = [-1,-100,3,99], k = 2
  输出:[3,99,-1,-100]
  解释:
    向右轮转 1 步: [99,-1,-100,3]
    向右轮转 2 步: [3,99,-1,-100]

提示:

  • 1 <= nums.length <= 1 0 5 10^5 105
  • - 2 31 2^{31} 231 <= nums[i] <= 2 31 2^{31} 231 - 1
  • 0 <= k <= 1 0 5 10^5 105


进阶:

  • 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
  • 你可以使用空间复杂度为 O ( 1 ) O(1) O(1)原地 算法解决这个问题吗?

官方解法:

  1. 用额外的数组。
    遍历原数组,把下标为i的元素放到新数组下表为(i+k)%n的位置,再把数据拷贝回原数组。
class Solution {
    public void rotate(int[] nums, int k) {			// 注意这里的坑 void是无返回值的
        int length = nums.length;
        int[] out = new int[length];
        int index;
        for(int i=0; i<length; i++){        		// 每个数逐次转移
        	out[(i+k)%length] = nums[i];			// 用取余的方法获得转移后的位置
        }
        System.arraycopy(out, 0, nums, 0, length);
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 为数组的长度。
  • 空间复杂度: O ( n ) O(n) O(n)

补充:

  • System.arraycopy(src, srcPos, dest, destPos, length);
    src:源数组;
    srcPos:源数组要复制的起始位置;
    dest:目的数组;
    destPos:目的数组放置的起始位置;
    length:复制的长度
  1. 环状替换

在这里插入图片描述在这里插入图片描述

class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k = k % n;											// 转移k次和转移k%n次效果相同
        int count = gcd(k, n);								// count是已经访问过的元素量。gfd()是求最大公约数
        for (int start = 0; start < count; ++start) {		// 一次循环表示从0出发到回到0.循环次数由nums长度和k的最大公约数决定(超过最大公约数的话就重复替换了)
            int current = start;
            int prev = nums[start];
            do {
                int next = (current + k) % n;				// 被替换的位置
                int temp = nums[next];						// 先把被替换的位置的值取出来暂存
                nums[next] = prev;							// 替换
                prev = temp;
                current = next;								// 当前位置
            } while (start != current);						// 当前位置是回到了出发点就不再循环了
        }
    }

    public int gcd(int x, int y) {
        return y > 0 ? gcd(y, x % y) : x;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为数组的长度。每个元素只会被遍历一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)。我们只需常数空间存放若干变量。

补充:

  • do{}while()是先无条件执行一次do,再判断是否继续循环下去。
  • while(){}是先判断。


吐槽:不喜欢这种做法,好复杂哦

  1. 数组翻转
    在这里插入图片描述
class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k = k % n;      										// 转移k次和转移k%n次效果一样
        reverse(nums,0,n-1);
        reverse(nums,0,k-1);
        reverse(nums,k,n-1);    
    }
    public void reverse(int[] nums, int left, int right){       // 把nums[left,right]全翻转
        while(left < right){      								// 不取等号,因为中间值不需要翻转
            int temp = nums[right];      						// 把后面数暂存
            nums[right] = nums[left];
            nums[left] = temp;
            left++;
            right--;
        }
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为数组的长度。每个元素被翻转两次,一共 n n n 个元素,因此总时间复杂度为 O ( 2 n ) = O ( n ) O(2n)=O(n) O(2n)=O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

神奇而简单的做法!!!

注意:

  • 新定义的函数reverse()是和rotate()并列的,不是包含于的关系。
  • 是while(left < right)不是for() (自己练习的时候写错了)

(3)移动零(283)★

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意,必须在不复制数组的情况下原地对数组进行操作。

示例 1:
  输入: nums = [0,1,0,3,12]
  输出: [1,3,12,0,0]

示例 2:
  输入: nums = [0]
  输出: [0]

提示:
  1 <= nums.length <= 1 0 4 10^4 104
  - 2 3 1 2^31 231 <= nums[i] <= 2 3 1 2^31 231 - 1

**进阶:**你能尽量减少完成的操作次数吗?

个人解法:

class Solution {
    public void moveZeroes(int[] nums) {
        int n = nums.length;
        for(int i=0; i<n; i++){				
            if(nums[i]==0){					// 找到第一个是0的数
                for(int j=i; j<n; j++){
                    if(nums[j]!=0){			// 从它后面找第一个非0的数
                        nums[i]=nums[j];	// 0和非0数交换位置
                        nums[j]=0;
                        break;				// 跳出找非0数的循环,重新找0
                    }
                }
            }
        }
    }
}

官方解法:

在这里插入图片描述

class Solution {
    public void moveZeroes(int[] nums) {
        int n = nums.length, left = 0, right = 0;
        while (right < n) {
            if (nums[right] != 0) {						// 如果右值不为零
                swap(nums, left, right);				// nums[left]和nums[right]交换
                left++;									// 左指针右移
            }
            right++;									// 右指针右移
        }
    }
    public void swap(int[] nums, int left, int right) {
        int temp = nums[left];							
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为序列长度。每个位置至多被遍历两次。
  • 空间复杂度: O ( 1 ) O(1) O(1)。只需要常数的空间存放若干变量。

思考:

  • 这种方法必须用temp,而不是直接把nums[right]赋0。因为也可能数组里并没有0

其他方法:

class Solution {
    public void moveZeroes(int[] nums) {
        int indexNow = 0;
        int indexNum = 0;
        int m = nums.length;
		// -------- 先把非0数全挪到前面 --------
        while(indexNum<m){
            if(nums[indexNum] != 0) {
                nums[indexNow++] = nums[indexNum];
            }
            ++indexNum;
        }
		// -------- 再把后面的0补齐 --------
        for(int i = indexNow; i < m; i++){
            nums[i] = 0;
        }
    }
}

(4)两数之和II - 输入有序数组(167)★★

给你一个下标从 1 开始的整数数组numbers,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数target的两个数。如果设这两个数分别是numbers[index1]numbers[index2],则1 <= index1 < index2 <= numbers.length
以长度为 2 的整数数组[index1, index2]的形式返回这两个整数的下标 index1index2
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。

示例 1:
 输入:numbers = [2,7,11,15], target = 9
 输出:[1,2]
 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

示例 2:
 输入:numbers = [2,3,4], target = 6
 输出:[1,3]
 解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。

示例 3:
 输入:numbers = [-1,0], target = -1
 输出:[1,2]
 解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

提示:

  • 2 <= numbers.length <= 3 * 1 0 4 10^4 104
  • -1000 <= numbers[i] <= 1000
  • numbers 按 非递减顺序 排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案

个人解法(反面教材)

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0, right = 0;           // 下标从1开始
        int len = numbers.length;
        int[] out = new int[2];
        for(left=0; left<len; left++){           // 左数取第一个
            for(right=left+1; right<len; right++) {     // 右数从左数下一个开始找
                int add = numbers[left]+numbers[right];
                if(add == target){      // 如果和是目标值
                    out[0]=left+1;
                    out[1]=right+1;
                    return out;
                }else if(add >= target){    // 如果和大于目标值,右标不必再右移了,开始移左标
                    break;
                }
            }
        }
        return out;
    }
}

额 提交结果是“超出时间限制”

官方解法:

  1. 二分查找

在这里插入图片描述

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        for (int i = 0; i < numbers.length; ++i) {
            int low = i + 1, high = numbers.length - 1;
            while (low <= high) {
                int mid = (high - low) / 2 + low;
                if (numbers[mid] == target - numbers[i]) {
                    return new int[]{i + 1, mid + 1};
                } else if (numbers[mid] > target - numbers[i]) {
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
        }
        return new int[]{-1, -1};
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n是数组的长度。需要遍历数组一次确定第一个数,时间复杂度是 O ( n ) O(n) O(n),寻找第二个数使用二分查找,时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),因此总时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 空间复杂度:O(1)O(1)。
  1. 双指针

在这里插入图片描述

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int low = 0, high = numbers.length - 1;
        while (low < high) {
            int sum = numbers[low] + numbers[high];
            if (sum == target) {
                return new int[]{low + 1, high + 1};
            } else if (sum < target) {
                ++low;
            } else {
                --high;
            }
        }
        return new int[]{-1, -1};
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n是数组的长度。两个指针移动的总次数最多为 n n n次。
  • 空间复杂度: O ( 1 ) O(1) O(1)

(5)反转字符串(344)★

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组s的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O ( 1 ) O(1) O(1)的额外空间解决这一问题。

示例 1:
 输入:s = [“h”,“e”,“l”,“l”,“o”]
 输出:[“o”,“l”,“l”,“e”,“h”]

示例 2:
 输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
 输出:[“h”,“a”,“n”,“n”,“a”,“H”]

提示:

  • 1 <= s.length <= 1 0 5 10^5 105
  • s[i] 都是 ASCII 码表中的可打印字符

个人解法:

class Solution {
    public void reverseString(char[] s) {
        int len = s.length;     			// 字符个数
        int left = 0, right = len - 1;    	// 左右指针
        while(left<right){                  // 不需要取等号,中间位置的不用交换
            char temp = s[right];
            s[right] = s[left];
            s[left] = temp;
            left++;
            right--;
        }
    }
}

挺简单的,答得很完美,嘻嘻
  在这里插入图片描述

官方解法:

class Solution {
    public void reverseString(char[] s) {
        int n = s.length;
        for (int left = 0, right = n - 1; left < right; ++left, --right) {
            char tmp = s[left];
            s[left] = s[right];
            s[right] = tmp;
        }
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为字符数组的长度。一共执行了 n / 2 n/2 n/2次的交换。
  • 空间复杂度: O ( 1 ) O(1) O(1)。只使用了常数空间来存放若干变量。

(6)反转字符串中的单词III(557)★

 给定一个字符串s,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:
 输入:s = “Let’s take LeetCode contest”
 输出:“s’teL ekat edoCteeL tsetnoc”

示例 2:
 输入: s = “God Ding”
 输出:“doG gniD”

提示:

  • 1 <= s.length <= 5 * 1 0 4 10^4 104
  • s 包含可打印的 ASCII 字符。
  • s 不包含任何开头或结尾空格。
  • s 里 至少 有一个词。
  • s 中的所有单词都用一个空格隔开。

官方解法:

使用额外空间

class Solution {
    public String reverseWords(String s) {
        StringBuffer ret = new StringBuffer();
        int length = s.length();
        int i = 0;
        while (i < length) {
            int start = i;
            while (i < length && s.charAt(i) != ' ') {
                i++;
            }
            for (int p = start; p < i; p++) {
                ret.append(s.charAt(start + i - 1 - p));
            }
            while (i < length && s.charAt(i) == ' ') {
                i++;
                ret.append(' ');
            }
        }
        return ret.toString();
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为字符串的长度。原字符串中的每个字符都会在 O ( 1 ) O(1) O(1)的时间内放入新字符串中。
  • 空间复杂度: O ( n ) O(n) O(n)。我们开辟了与原字符串等大的空间。

吐槽: 很烦,Java字符串不能像字符数组那样原地操作

(7)链表的中间结点(876)★

给定一个头结点为head的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

示例 1:
 输入:[1,2,3,4,5]
 输出:此列表中的结点 3 (序列化形式:[3,4,5])
 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
 注意,我们返回了一个 ListNode 类型的对象 ans,这样:
 ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

示例 2:
 输入:[1,2,3,4,5,6]
 输出:此列表中的结点 4 (序列化形式:[4,5,6])
 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

提示:

  • 给定链表的结点数介于 1 和 100 之间。

二、LeetCode热题HOT 100

   传送门:HOT100

1、两数之和 ★

给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

示例 1:
 输入:nums = [2,7,11,15], target = 9
 输出:[0,1]
 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:
 输入:nums = [3,2,4], target = 6
 输出:[1,2]

示例 3:
 输入:nums = [3,3], target = 6
 输出:[0,1]

提示:

  • 2 <= nums.length <= 1 0 4 10^4 104
  • - 1 0 9 10^9 109 <= nums[i] <= 1 0 9 10^9 109
  • - 1 0 9 10^9 109 <= target <= 1 0 9 10^9 109
  • 只会存在一个有效答案


进阶: 你可以想出一个时间复杂度小于 O ( n 2 ) O(n^2) O(n2) 的算法吗?

个人解法:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int len = nums.length;
        int[] out = new int[2];
        for(int i=0; i<len; i++){
            int newtarget = target - nums[i];       // 定一个点,从它后面找目标值
            for(int j=i+1; j<len; j++){
                if(nums[j]==newtarget){             // 如果找到了
                    out[0] = i;
                    out[1] = j;
                    return out;
                }
            }
        }
        return out;
    }
}

最笨的方法

官方解法:

  1. 暴力枚举
    在这里插入图片描述
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)

2. 哈希表

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();		// Map<K, V>,K和V分别是Key和Value的数据类型.这里都是int型
        for (int i = 0; i < nums.length; ++i) {
            if (hashtable.containsKey(target - nums[i])) {
                return new int[]{hashtable.get(target - nums[i]), i};
            }
            hashtable.put(nums[i], i);
        }
        return new int[0];
    }
}

哈希表和字典好像啊。

补充: Java哈希表入门Java中哈希表之HashMap的常见用法及原理

2、两数相加 ★★

给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:
   在这里插入图片描述
 输入:l1 = [2,4,3], l2 = [5,6,4]
 输出:[7,0,8]
 解释:342 + 465 = 807.

示例 2:
 输入:l1 = [0], l2 = [0]
 输出:[0]

示例 3:
 输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
 输出:[8,9,9,9,0,0,0,1]

提示:

  • 每个链表中的节点数在范围 [1, 100] 内
  • 0 <= Node.val <= 9
  • 题目数据保证列表表示的数字不含前导零

 官方解法:
在这里插入图片描述

/**
 * Definition for singly-linked list.		单链表
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
 class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null, tail = null;
        int carry = 0;
        while (l1 != null || l2 != null) {
            int n1 = l1 != null ? l1.val : 0;
            int n2 = l2 != null ? l2.val : 0;
            int sum = n1 + n2 + carry;
            if (head == null) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail.next = new ListNode(sum % 10);
                tail = tail.next;
            }
            carry = sum / 10;
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        if (carry > 0) {
            tail.next = new ListNode(carry);
        }
        return head;
    }
}

复杂度分析

  • 时间复杂度: O ( max ⁡ ( m , n ) ) O(\max(m,n)) O(max(m,n)),其中 m m m n n n分别为两个链表的长度。我们要遍历两个链表的全部位置,而处理每个位置只需要 O ( 1 ) O(1) O(1)的时间。
  • 空间复杂度: O ( 1 ) O(1) O(1)。注意返回值不计入空间复杂度。

救命好难

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值