文章目录
一、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 中因此返回
提示:
- 你可以假设
nums
中的所有元素是不重复的。n
将在[1, 10000]
之间。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) 的算法解决本问题
官方解法:
- 最简单粗暴的方法是循环计算平方数后,调用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
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)。除了存储答案的数组以外,我们只需要维护常量空间。
- 双指针法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)的 原地 算法解决这个问题吗?
官方解法:
- 用额外的数组。
遍历原数组,把下标为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:复制的长度
- 环状替换
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(){}是先判断。
吐槽:不喜欢这种做法,好复杂哦
- 数组翻转
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]
的形式返回这两个整数的下标index1
和index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 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;
}
}
额 提交结果是“超出时间限制”
官方解法:
- 二分查找
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)。
- 双指针
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;
}
}
最笨的方法
官方解法:
- 暴力枚举
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)。注意返回值不计入空间复杂度。
救命好难