LeeCode数据结构
Solution1两数之和、Solution15三数之和、Solution26删除排序数组中的重复项、Solution27移除元素、Solution80删除有序数组的重复项Ⅱ、Solution88合并两个有序数组、Solution167两数之和Ⅱ输入有序数组、Solution53最大子数组和、Solution121买卖股票的最大收益、Solution217存在重复元素、Solution242有效字母异位词、Solution349两数组交集、
Solution1两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
两种思路 暴力解法,双指针循环嵌套直到找到结果;
哈希表让要找的数减当前的数,拿他们的值在已有哈希表中找对应值,有返回无继续。
public class S1两数之和 {
//哈希表
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
if (i == 0) {
map.put(nums[i], i);
} else {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
} else {
map.put(nums[i], i);
}
}
}
return null;
}
//双指针 循环
public int[] twoSum01(int[] nums, int target) {
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return null;
}
}
Solution15三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
双指针 按排序左小右大计算当前首位加次位加末位数是否为零进行操作
需要考虑的细节太多!!
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length;i++){
if (nums[i] > 0){//判正负
return lists;
}
if(i > 0 && nums[i] == nums[i - 1]){//判重复
continue;
}
//每次外循环结束LR都会重置
int cur = nums[i];
int L = i + 1;
int R = nums.length -1;
while (L < R) {//相遇自动跳出
int temp = cur + nums[L] + nums[R];
if (temp == 0) {//传输当前组合
List<Integer> list = new ArrayList<>();
list.add(cur);
list.add(nums[L]);
list.add(nums[R]);
lists.add(list);
L++;
R--;
while (L < R && nums[L] == nums[L - 1]){
L++;
}
while (L < R && nums[R] == nums[R + 1]){
R--;
}
}else if (temp > 0){//正减小 负增大
R--;
}else {
L++;
}
}
}
return null;
}
Solution26删除排序数组中的重复项
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
使用双指针法完成,一个指针向后遍历,一个指针原地重写数组即可。
public int removeDuplicates(int[] nums) {
int n = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[n] != nums[i]){
n += 1;
nums[n] = nums[i];
}
}
return n + 1;
}
Solution27移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
双指针前验后替
//给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
public class S27移除元素 {
public int removeElement(int[] nums, int val) {
int n = -1;//防止第一个就是要删的
for (int i = 0; i < nums.length; i++) {
if (nums[i] != val){
n++;
nums[n] = nums[i];
}
}
return n + 1;
}
}
Solution80删除有序数组的重复项Ⅱ
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
首先前两个直接跳过,重不重复都无意义,始终拿后指针遍历到的位置元素与当前指针前两位元素比较,需要添加就替换当前值。
public int removeDuplicates(int[] nums) {
int index = 2;
for (int i = index; i < nums.length; i++) {
if (nums[i] > nums[index - 2]){//不同就替换当前位置
nums[index] = nums[i];
index++;
}
}
return index;
}
Solution88合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
直接连接然后排序
public void merge(int[] nums1, int m, int[] nums2, int n) {
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
Arrays.sort(nums1);
}
奇怪的解法
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m--+--n;
while(n>=0) {
nums1[i--] = m>=0 && nums1[m]>nums2[n] ? nums1[m--] : nums2[n--];
}
}
Solution167两数之和Ⅱ输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
二分查找利用原本就升序的数组,双指针把数值范围确定出来然后进行操作。
时间复杂度Onlogn略高。
public int[] twoSum01(int[] numbers, int target) {
for (int i = 0; i < numbers.length; i++) {
int low = i + 1;
int high = numbers.length - 1;
if (target - numbers[i] > numbers[high]){
continue;//差比最大数还大就跳过
}
if (target - numbers[i] < numbers[low]){
break;//差比当前还小直接退出
}
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};
}
还可以用双指针,更快更强
public int[] twoSum(int[] numbers, int target) {
int low = 0;
int high = numbers.length - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum < target){
low++;
}else if (sum > target){
high--;
}else{
return new int[]{low + 1, high + 1};
}
}
return null;
}
Solution53最大子数组和
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
思路:res作为历史最佳解,sum作为当前最佳解,每一次遍历nums数组时,都去动态更新res和sum。 动态更新的逻辑为: 如果sum为正数,在有res记录历史最佳值的条件下,可以有恃无恐地继续累加,创造新高;如果sum为负数,不管下一次遍历值是多少累加后都不会大于它,见风使舵果断取下一个遍历值为当前最佳解。 每一轮遍历结束后,如果当前最佳解优于历史最佳解,就会升任历史最佳解。 动!!态!!规!!划!!
//如果sum为正数,那么继续向后累加并记录下sum的最大值,继续向后累加的原因是sum有变大的可能。
//如果sum为负数,那么sum+nums[i]必然小于nums[i],所以sum = nums[i].
public int maxSubArray(int[] nums) {
int res = nums[0];
int sum = 0;
for (int num : nums) {
if (sum > 0)
sum += num;
else
sum = num;
res = Math.max(res, sum);
}
return res;
}
Solution121买卖股票的最大收益
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
动态规划 前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
//低价买高价卖
//记录【今天之前买入的最小值】
//计算【今天之前最小值买入,今天卖出的获利】,也即【今天卖出的最大获利】
//比较【每天的最大获利】,取最大值即可
public class S121买卖股票 {
public int maxProfit(int[] prices) {
if(prices.length <= 1)
return 0;
int min = prices[0], max = 0;
for(int i = 1; i < prices.length; i++) {
max = Math.max(max, prices[i] - min);
min = Math.min(min, prices[i]);
}
return max;
}
}
Solution217存在重复元素
给你一个整数数组
nums
。如果任一值在数组中出现 至少两次 ,返回true
;如果数组中每个元素互不相同,返回false
。
方法一 数组排序
排序在对数字从小到大排序之后,数组的重复元素一定出现在相邻位置中。因此,我们可以扫描已排序的数组,每次判断相邻的两个元素是否相等,如果相等则说明存在重复的元素。
public boolean containsDuplicate(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
for (int i = 0; i < n - 1; i++) {
if (nums[i] == nums[i + 1]) {
return true;
}
}
return false;
}
方法二 HashSet
时间空间复杂度都是O(n).
//HashSet 利用哈希表不会存储两个相同元素的特性
public boolean containsDuplicate01(int[] nums) {
HashSet<Integer> hashSet = new HashSet<>();
for (int num : nums) {
if (hashSet.add(num) == false) {
return true;
}
}
return false;
}
Solution242有效字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
import java.util.Arrays;
//给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
public class S242有效的符号 {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
Arrays.sort(sChars);
Arrays.sort(tChars);
return Arrays.equals(sChars, tChars);
}
}
Solution349两数组交集
给定两个数组
nums1
和nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
总结 先排序,然后是用双指针先遍历两容器,重复则插入到新容器
时间复杂度:O(m \log m+n \log n)O(mlogm+nlogn)
细节:对两个数组进行排序,然后使用两个指针遍历两个数组。可以预见的是加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,我们需要额外记录变量 \textit{pre}pre 表示上一次加入答案数组的元素。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,且该数字不等于 \textit{pre}pre ,将该数字添加到答案并更新 \textit{pre}pre 变量,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
public int[] intersection(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int length1 = nums1.length, length2 = nums2.length;
int[] intersection = new int[length1 + length2];
int index = 0, index1 = 0, index2 = 0;
while (index1 < length1 && index2 < length2) {
int num1 = nums1[index1], num2 = nums2[index2];
if (num1 == num2) {
// 保证加入元素的唯一性
if (index == 0 || num1 != intersection[index - 1]) {
intersection[index++] = num1;
}
index1++;
index2++;
} else if (num1 < num2) {
index1++;
} else {
index2++;
}
}
return Arrays.copyOfRange(intersection, 0, index);
}