决定开始刷起听闻了很久缺一直没动手来做的LeetCode,
借鉴知乎上大佬们的说法,
还有自己基础比较差的原因(Java数组新建语法都能写错),
自己决定按LeetCode上
标签:数组 -> 字符串 -> 链表 -> 数学 -> 树(然后再是动态规划那些,留下菜鸡的泪水),
难度:简单 -> 中等 的顺序刷下热门题(困难难度的看缘分吧,起步晚了,需要准备秋招,所以挑“性价比”高的来先)
现在进入正题:
题目描述:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
(你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。)
这句“数组中同一个元素不能使用两遍”,我一开始以为代码中对同一个元素只能操作一次(我笨然后理解错了)。后来看官方解题的意思是:像数组中有一个数组下标是1的元素“3”,输入的target是“6”,不存在返回两个数组下标1的情况。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/two-sum
题解:
1.这是算法处男AC代码:(时间复杂度:O(n^2), 空间复杂度:O(1))
作为一个笔试算法题从未ac过的男人,上来不管三七二十几,就是一手暴力解法,只为看到“通过”两个字。
结果开始的时候数组的构造语法还忘了,了。
class Solution {
public int[] twoSum(int[] nums, int target) {
// 枚举数组中非同一个元素相加的所有情况
//返回满足相加为target的两元素数组下标
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{
i, j};
}
}
}
return null;
}
}
2.参考官方题解后的代码:(时间复杂度:O(n) ,空间复杂度:O(n))
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numMap = new HashMap<>();
// 第一次遍历将数组中的元素放入map中
// key放数组中的元素值、value放对应的下标
for (int i = 0; i < nums.length; i++) {
numMap.put(nums[i], i);
}
// 第二次遍历从map中取出满足条件的元素
for (int j = 0; j< nums.length; j++) {
int secondNum = target - nums[j];
// 判断元素是否存在且不是当前遍历元素
if (numMap.containsKey(secondNum) && numMap.get(secondNum) != j) {
// 数组放回满足条件的两个元素的下标
return new int[]{
numMap.get(secondNum), j};
}
}
return null;
}
}
3.这是只需要遍历一次即可求解的代码:(时间复杂度:O(n), 空间复杂度:O(n))
遍历数组的同时将nums[ i ]放入map中,
key放数组中的元素值、value放对应的下标。
不必提前将所有元素放入map(题解2的第一次遍历),若数组中有两元素满足条件nums[ j ] = target - nums[ i ],遍历到 j 元素或 i 元素,必定有其一在此前已放入map中。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int secondNum = target - nums[i];
// 判断满足条件的元素此前是否已被放入map中
if (numMap.containsKey(secondNum)) {
// map中存在,返回两元素数组下标
return new int[]{
numMap.get(secondNum), i};
}
// 不存在,放入map中,进行下一次遍历
numMap.put(nums[i], i);
}
return null;
}
}
题目描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
(最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。)
注意:你不能在买入股票前卖出股票。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
题解:
1.暴力(时间复杂度:O(n^2), 空间复杂度:O(1))
刷的题太少了,没啥好思路,暴力解不复杂而且题目对时间复杂度没啥要求的话基本就直接暴力了
class Solution {
public int maxProfit(int[] prices) {
// 记录最大利润
int maxProfit = 0;
// 计算第i天买入,第j天卖出的所有可能的利润结果
for (int i = 0; i < prices.length; i++) {
for(int j = i+1; j < prices.length; j++) {
int currentProfit = prices[j] - prices[i];
if(maxProfit< currentProfit) {
maxProfit= currentProfit;
}
}
}
return maxProfit ;
}
}
2.动态规划(时间复杂度:O(n), 空间复杂度:O(n))
讨论区中提到动态规划的解题思路,自己就按照解题思路搞一手,之前看过背包问题,这里写的时候就直接把每个子问题的解放进dp[ i ]中了(然后发现没必要)
思路:
dp[ i ] 存放前 i 天中股票的最低价
prices[ i ]存放第 i 天股票价格
则有递推公式:dp[ i ] = min (dp[ i-1], prices[ i ])
(此prices[ i ]的 i 从1开始,与下面题解prices[ i ]不是同一个)
class Solution {
public int maxProfit(int prices[]) {
if (prices.length == 0) return 0;
int[] dp = new int[prices.length + 1];
dp[0] = prices[0];
int maxProfit = 0;
for (int i = 1 ; i <= prices.length;i++) {
dp[i] = Math.min(dp[i-1], prices[i-1]);
// 假设今天卖出,所获得的利润是否在当前最大
if (prices[i-1] - dp[i] > maxProfit) {
maxProfit = prices[i-1] - dp[i];
}
}
return maxProfit;
}
}
用一个变量记录最小的那个价格就可以♂️ (时间复杂度:O(n), 空间复杂度:O(1))
class Solution {
public int maxProfit(int prices[]) {
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
// 记录【今天之前股票的最小值】
if (prices[i] < minprice) {
minprice = prices[i];
}
// 比较【最大获利】,取最大值
if (prices[i] - minprice > maxprofit) {
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
}
}
题目描述:
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
(你可以假设数组是非空的,并且给定的数组总是存在多数元素。)
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/majority-element
题解:
这道题终于第一想法不是暴力解了
前面“两数之和”的题目用Map的思路突然间给了我灵感,冥冥之中感觉上天叫我再用Map,
最后官方题解也是用Map(不是最佳解法)就感觉自己是个英雄,还很帅(大佬们原谅身为菜鸡的骄傲)
Map-key存放数据元素,value存放出现次数。
1.使用HashMap一次遍历解法(时间复杂度:O(n),空间复杂度:O(n))
class Solution {
public int majorityElement(int[] nums) {
Map<Integer, Integer> numMap = new HashMap<>(1000);
for (int num : nums) {
if (numMap.containsKey(num)) {
//覆盖原来Map中的键值对,出现次数+1
numMap.put(num, numMap.get(num) +1);
} else {
numMap.put(num, 1);
}
if (numMap.get(num) > nums.length / 2) {
return num;
}
}
return -1;
}
}
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/move-zeroes
题解:
1.双指针交换元素(时间复杂度:O(n),空间复杂度:O(1))
class Solution {
public void moveZeroes(int[] nums) {
for (int p = 0, i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
int temp = nums[i];
nums[i] = nums[p];
nums[p++] = temp;
}
}
}
}
当数组中存在元素0的时候,p在遍历过程中会指向从数组头开始的第一个0。
有个好处,当多个0相邻时,交换第一个0就行,不用0和0交换位置。
2.也可暴力直接解-(时间复杂度:O(n^2),空间复杂度:O(1))
问题:一开始从数组头移动0会出现问题,当两个0相邻时,一个0可以移动到最后,但相邻的那个0被放到前面了。
解决:从数组尾开始移动0
class Solution {
public void moveZeroes(int[] nums) {
for (int j = nums.length - 1; j >= 0; j--) {
if (nums[j] == 0) {
int middle = 0;
for (int i = j + 1; i < nums.length; i++) {
middle = nums[i];
nums[i] = nums[i-1];
nums[i-1] = middle;
}
}
}
}
}
题目描述:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
题解:
1.动态规划(时间复杂度:O(n),空间复杂度:O(1))
思路: dp[ i ] 记录以元素i结尾的子数组最大和
递推公式:dp[ i ] = Math.max(nums[ i ], dp[ i-1 ] + nums[ i ] )
这里还是不需要记录所有子问题的结果,
resultMax记录已解出的子问题中的最大值即可。
class Solution {
public int maxSubArray(int[] nums) {
int currentMax = nums[0];
int resultMax = nums[0];
for (int i = 1; i < nums.length; i++) {
currentMax = Math.max(currentMax + nums[i],nums[i]);
if(nowMax > allMax) {
resultMax = nowMax;
}
}
return allMax;
}
}
题目描述:
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array
题解:
1.“ 您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗?”
- “我不能。”
一开始没有其他想法,还是开了个同长度的数组。
因为元素a [ i ]的范围在1-n,假设无重复元素,新数组为b [ j ],则可以利用数组b [ j ]存放a数组中的元素,使元素下标和值对应,即数组b [a [ i ] ] = a [i];
然后再遍历一次新数组,没有被新赋值的元素下标即为原数组中不存在的值。
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int[] newNums = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
newNums[nums[i] - 1] = nums[i];
}
List<Integer> res = new ArrayList<>();
for (int j = 0; j < nums.length; j++) {
if (newNums[j] == 0) {
res.add(j + 1);
}
}
return res;
}
}
官方题解的第一个解法是用了Map,其实也是开了新的空间,所以我想题目的意思是”最好能不使用额外空间“,不能的话开心就好
官方题解:
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
HashMap<Integer, Boolean> hashTable = new HashMap<Integer, Boolean>();
// 遍历一遍将数组中的元素放入Map中
for (int i = 0; i < nums.length; i++) {
hashTable.put(nums[i], true);
}
List<Integer> result = new LinkedList<Integer>();
// 遍历范围1 <= i <= nums.length,不在Map中的i即为不存在数组nums中的值
for (int i = 1; i <= nums.length; i++) {
if (!hashTable.containsKey(i)) {
result.add(i);
}
}
return result;
}
}
2. 在原数组上进行修改
将以数组元素值为下标的数组元素修改为其负数(nums [ nums [ i ] ] = -nums [ nums [ i ] ])
再遍历一遍数组,若nums [ i ]不为负数,证明不存在元素值为i + 1的数组元素。
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 数组中偏后的元素,在被遍历之前有可能被修改为负数。避免数组越界问题,取绝对值
int index = Math.abs(nums[i]) -1 ;
// 当数组中有重复元素i,第一次修改时nums[ i ]已为负数,避免负负得正
nums[index] = -Math.abs(nums[index]);
}
List<Integer> res = new ArrayList<>();
for (int j = 0; j < nums.length; j++) {
if (nums[j] > 0) {
res.add(j + 1);
}
}
return res;
}
}
题目描述:
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入:
[2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9]
进行升序排序,那么整个表都会变为升序排序。
说明 :
输入的数组长度范围在 [1, 10,000]。 输入的数组可能包含重复元素 ,所以升序的意思是<=。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray
题解:
1.从左到右遍历,找出需要调整位置的元素的最大下标;从右到左遍历,找出需要调整位置的元素的最小下标;其中间序列即为需要重新排序的数组元素集
class Solution {
public int findUnsortedSubarray(int[] nums) {
// 存在重复元素,需引入max和min,避免重复元素的位置不被记录到
int high = 0,low = 0,max = nums[0],min = nums[nums.length-1];
for (int i = 1; i < nums.length; i++) {
if (max < nums[i]) {
max = nums[i];
}
if (min > nums[nums.length-i]) {
min = nums[nums.length-i];
}
if (nums[i] < max) {
high = i;
}
if (nums[nums.length-1-i] > min) {
low = nums.length-1-i;
}
}
// 若数组原本就是升序,无需调整
return high > low ? high-low+1 : 0;
}
}
---------------------------------------------------------------手动分割--------------------------------------------------------------
这其中有我自己对题目和参考算法的理解,表达的意思可能不是很准确还有可能存在错误,有问题请提出,感谢❤
也希望自己可以坚持下去做记录和复习!