初级算法
LeetCode 官方推出的经典面试题目清单 —— 「初级算法 - 帮助入门」
通过力扣的这个卡片 ,入门算法。
下面是个人刷题的记录与总结,这里会记录比较有代表性和比较好的题目
首先来看第一题,删除排序重复值
删除排序数组中的重复项 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
看到这题,比较好想的思路是,定义一个记录指针,直接遍历一遍数组,每次保存上一个遍历的值,当遍历指针发现指向的数字不等于上个保存的值时,把上一个值保存到记录指针的位置。
public static int removeDuplicates(int[] nums) {
if(nums==null||nums.length==0){
return 0;
}
//count为记录指针
int tmp=nums[0],count=0;
for(int i=0;i<nums.length;i++){//i为遍历指针
//tmp用于记录相同元素
if(tmp==nums[i]){
continue;
}
//遇到不相同元素 先保存之前的元素
nums[count]=tmp;
tmp=nums[i];
count++;
}
nums[count]=tmp;//记得保存最后一个值
return count+1;
}
但一种更好的解决办法是双指针,也比较好理解,双指针是很多数组题/字符串题都比较好的解决方法。
public int removeDuplicates1(int[] nums) {
if (nums.length == 0) {
return 0;
}
//保存结果的指针
int i = 0;
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {//j为遍历指针,此时为遍历到不同的元素
i++;//结果指针右移
nums[i] = nums[j];
}
}
return i + 1;
}
再看第二题,买卖股票的最佳时机II(注意,这里是可以尽可能的完成更多交易)
这题其实还有动态规划解,到后面讲到动态规划的时候再讲。
买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 =
5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 示例 2:输入: [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 =
5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
/**
* 个人双指针+贪心解决
* 思路 start代表当天,end代表未来某天
* start必须满足小于end
* 如果当天比未来价格高 不买 ,start++ 到下一天
* 只要碰到未来任何一天比start代表的价格高 直接卖出
* @param prices
* @return
*/
public static int maxProfit(int[] prices) {
int start=0,end=1,allMoney=0,money;
for(end=1;end<prices.length;end++){
//
if(start>=end){
continue;
}
//当天比未来价格高 左指针右移
if(prices[start]>=prices[end]){
start++;
continue;
}
//计算赚的钱
money=prices[end]-prices[start];
start++;
allMoney+=money;
}
return allMoney;
}
第三题是旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [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:输入: [-1,-100,3,99] 和 k = 2 输出: [3,99,-1,-100] 解释: 向右旋转 1 步:
[99,-1,-100,3] 向右旋转 2 步: [3,99,-1,-100]
我的想法是遍历一遍所有数字,计算所有数字的新下标,放在等大小的新数组里,如果下标超出数组限制,则将下标取余。
public static void rotateThink2(int[] nums, int k) {
int index;
int tmp[]= Arrays.copyOf(nums, nums.length);
for(int i=0;i<nums.length;i++){
index=i+k;//计算对应元素新位置的下标
if(index>nums.length-1){
index=index%(nums.length);
}
nums[index]=tmp[i];
}
}
但是题解的解法非常让人震撼,我们可以想一下,对于一个数组,向右边移动几个数字,就等于将超出边界的所有数字翻转放在数组首部,那我们换个思路想,先翻转整个数组,再将前k个数组翻转回来, 这时前k个元素就是倒数k个元素移动的新对应位置,最后再把 k到nums.length-1 个元素翻转回去则得到正确的结果(翻转数组只要定义两个指针,指向的位置元素互换则可)
public static void rotateSlowReverse(int[] nums, int k){
//当k比nums的长度大时 k的长度取余数
k %= nums.length;
//先反转整个数组
reverse(nums, 0, nums.length-1);
//反转前k个数字
reverse(nums, 0, k-1);
//反转数组后length-k
reverse(nums, k, nums.length-1);
}
public static void reverse(int[] nums,int start,int end){
int tmp;
while(start<end){
tmp=nums[start];
nums[start]=nums[end];
nums[end]=tmp;
start++;end--;
}
}
第四题 存在重复元素
给定一个整数数组,判断是否存在重复元素。 如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回
false 。
最容易想到的思路往往是排序,先排序,然后两个指针遍历数组,如果遇到相同值,则直接返回True ,最后返回false.
public static boolean containsDuplicate(int[] nums) {
//先排序 123345
Arrays.sort(nums);
int lastIndex=0;
for(int i=1;i<nums.length;i++){
if(nums[i]!=nums[lastIndex]){
lastIndex++;
}else{
return true;
}
}
return false;
}
当然也可以运用语言自带的API解决,比如我用的java,可以调用集合Set,直接把所有元素添加进集合,如果集合的长度与数组的长度不同,则返回true,否则返回false,这里就不发出代码了。
第五题 只出现一次的数字
只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1] 输出: 1 示例 2:
输入: [4,1,2,1,2] 输出: 4
最容易想到的思路和上题一样,就是先排序(java8默认排序是快排),排序后每两个一组进行遍历,最后如果只剩下最后一个元素,说明最后一个元素是唯一元素。 但快排的复杂度为nlogn 是非线性的。
public static int singleNumber(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
//1 1 2 2 3 3 4 5 5 6 6
Arrays.sort(nums);
for (int i = 0; i < nums.length - 1; i += 2) {
if (nums[i] != nums[i + 1]) {
return nums[i];
}
}
return nums[nums.length - 1];
}
第二个思路也和上一题类似,用Set集合解决,将Nums数组里的元素依次加入到Set中,每次加入的时候检查集合里是否存在该元素,如果存在,则移除集合中的元素,最后留在集合里的元素就是所要找的数字,这里就不提供代码了。
然后第三个思路,题解的一个很厉害的思路! 用异或符号来寻找题解,可能会有很多人连异或这个运算符都没有接触过,对于刚刷算法的我也一样,异或是比较二进制位,如果二进制位相同 则为0,不同则为1,举个例子
3^4 转化为二进制
011 ^ 100 = 111 = 7
异或满足结合律,a ^ b ^ c ^ d ^ e= a ^ e ^ b ^ d ^c
相同的数字异或运算后得0,所以直接将数组中所有数字进行异或操作,最后得到的就是唯一的那一个数字(任何数字和0异或都是该数字本身).
public static int t(int[] nums){
if(nums.length==1){
return nums[0];
}
int res = 0;
for(int t:nums){
res ^= t;
}
return res;
}
这是我第一次发博客,如果大家有什么问题可以在评论下面提出来,从今天开始养成记录下自己所学的内容的好习惯,以后会发布更多学习的笔记 ,下次再见!。