这是我第一次写博客,作为一位与众多人一样有一定C++语法基础的新手,我希望拿Leetcode来锻炼一下自己的编程能力与提高对数据结构的理解。我从这个星期就开始从Easy的数组题目开始做起并尝试写一下个人对这些题目的理解以及对一些优秀解法的解析。如果内容有什么不足或者错误,希望各位看官可以指出,非常感谢!
一.Plus One
Given a non-negative integer represented as a non-empty array of digits, plus one to the integer.
You may assume the integer do not contain any leading zero, except the number 0 itself.
The digits are stored such that the most significant digit is at the head of the list
简单来说,这道题就是给了一个数组表示一个整数number,然后在原有数组的基础上表示number+1的结果(十进制)。
解法思路:
类似于我们小学时候使用的加法进位,从最后一位开始算起,如果当前位有进位的话则下一位+1,当前位变为0,然后再看下一位+1后是否也会进位如此循环步骤。当第一位也会进位的话,就在数组的最前面插入一个1即可。
此时我们需要高度概括一下这个算法的核心步骤。
先说一下自己的最简单的想法:(比较基础,可以忽略)
1.先判断最后一位是否为9,若为9,则需要考虑到再前一位的进位可能性,若不为9,则直接返回最后一位+1的数组即可。
2.接下来就是对于在中间的数字的进位判断。我先设置一个flag给“前一位是否为9然后进位”做标记,若flag满足一定条件时则当前位+1,同时还要判断当前位原来是否为9。
3.重复以上步骤(循环即可)直至最后一步再判断是否需要在数组前面插入一个1。
因此,我写出了以下最初版的代码
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int flag = 0, n = digits.size() - 1;
if (digits[n] < 9) digits[n]++;
else { flag = 1;
digits[n] = 0;
if(n == 0) digits.insert(digits.begin(),1);
else {for(n = digits.size() - 2; n >= 0; n--)
{ if((digits[n]<9)&&(flag == 1)) flag = 0, digits[n] ++;
if((flag == 1)&&(digits[n] ==9)) digits[n] = 0;
if((flag == 1)&&(n==0)) digits.insert(digits.begin(),1);
}}
}
return digits; }
};
我相信这是基本所有人都能写出来的,当然也是最幼稚的。
这一个方法当然AC,是最快的,但是还是不够简洁与抽象。
因此我开始想,有没有更好的方法可以概括这一个过程而省去繁琐的判断。
其实这个问题的解决方法最核心的就是判断当前位上是否为9且后一位有没有进位。如果有进位,则当前位直接+1再进入下一次循环,若无,则返回修改后的数组即可。(及时地在发现已经没有进位的情况下返回数组可能帮助其运行时间缩短并简化代码)
因此,我开始想到了更加简洁的解法。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int n = digits.size();
for(int i = n -1 ; i >= 0; i--)
{ if(digits[i] == 9) digits[i] =0;
else
{digits[i]++;
return digits;}
}
digits.insert(digits.begin(),1);
return digits;
}
};
至于一些vector中的insert函数以及以后会用到的sort函数等等需要还未了解过的人再去看看,因为在此类题目这些都是经常会用到的。
二.Pascal’s Triangle II
Given an index k, return the kth row of the Pascal’s triangle.
For example, given k = 3,
Return [1,3,3,1].
Note:
Could you optimize your algorithm to use only O(k) extra space?
题目就是让我们求杨辉三角的第n行的数组。
基础解法的话,硬算是肯定可以的,但是会导致代码很长很丑陋。
我们需要运用数学上的一些已经发现的公式来计算杨辉三角。
第k行第n个数 = 第k行第n-1个数 * (行数 + 1 - n) / n
同时还有规律就是:首尾均为1,数组呈中心对称
因此写成代码即有:
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<int> r;
r.resize(rowIndex + 1); //resize为重新定义容器r的长度
r[0] = r[rowIndex] = 1;
for (int i = 1; i < (r.size() + 1) / 2; ++i) {
r[i] = r[rowIndex - i] = (unsigned long)r[i - 1] * (unsigned long)(rowIndex - i + 1) / i;
}
return r;
}
};
这一题没有什么太多好说的,数学的力量是无穷的!(逃
三.Array Partition I
Given an array of 2n integers, your task is to group these integers into n pairs of integer, say (a1, b1), (a2, b2), …, (an, bn) which makes sum of min(ai, bi) for all i from 1 to n as large as possible.
Example 1:
Input: [1,4,3,2]
Output: 4
Explanation: n is 2, and the maximum sum of pairs is 4 = min(1, 2) + min(3, 4).
Note:
n is a positive integer, which is in the range of [1, 10000].
All the integers in the array will be in the range of [-10000, 10000].
基本解法思路:
我们要使n个数对的最小值之和最大,则必定要使一个数对中的数相差不能太大,如果是较小的数搭配一个较大的数,那么这个较大的数就被浪费了。因此我们首要就是去排序,然后每两个元素取第一个元素即可。
因此我一开始写了一个最简单的代码:
int arrayPairSum(vector& nums)
{ int sum = 0;
sort(nums.begin(),nums.end());
for(int s = 0;s < nums.size();s += 2) sum += nums[s];
return sum;
}
这个程序用了vector自带的排序算法,在多次随机样本的测试中,这个算法会耗费大约平均20ms。拖慢了许多。
这时我就接触到了discuss区中他人的更加不错的解法,他用的是另一个数组来帮助排序。
接下来看看我对这一个算法的简单剖析。
class Solution {
public:
int arrayPairSum(vector<int>& nums) {
vector<int> a(20001,0);
for(size_t i=0;i<nums.size();i++)
{
a[nums[i]+10000]++;
}
int ret=0;
int flag=0;
for(size_t i=0;i<20001;){
if((a[i]>0)&&(flag==0)){
ret=ret+i-10000;
flag=1;
a[i]--;
}else if((a[i]>0)&&(flag==1)){
a[i]--;
flag=0;
}else i++;
}
return ret;
}
};
这一段代码一开始让我很迷茫,但是后来我发现实际上它走的也是先排序后取数对中的小数之和的套路。
vector<int> a(20001,0);
for(size_t i=0;i<nums.size();i++) { a[nums[i]+10000]++; }
这一段做的正是排序。首先申请一个长度为20001,所有元素为0的数组a,然后再将“nums数组内的数字 + 10000”作为数组a中元素的下标,最后将数组a中下标与nums有关的元素+1。就这样,依靠下标完成了对nums内无序数字的排序,小的数字对应的数组a的下标在前面,大的数字对应的下标在后面。
然后接下来的部分就是寻找数组a中不为0的数的下标并通过 “下标 = number + 10000”这一个式子来找回对应的数字并相加,同时通过flag来做记号表示每两个数中选择前一个,然后 a[i]– 是为了使算过的数在判断中为False而避免重复算数。
这个解法是复杂度是线性的,省去了自带sort的排序算法的时间耗费,非常值得学习。
四.Two Sum II - Input array is sorted
Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.
The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.
You may assume that each input would have exactly one solution and you may not use the same element twice.
Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2
这道题目我最基本的想法就是暴力遍历,每算一个数,就拿它与目标的差跟后面的元素对比即可。但是这样的算法耗时相当长,因此我在写了这一个基础解法之后我就在想既然是已经排好序的,那么为何不利用一下大小排序带来的便利呢?
于是就有了以下这个代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> list(2);
int i = 0,n = nums.size() - 1;
for(;i < n;)
{ if(nums[i] + nums[n] > target) n--;
else if(nums[i] + nums[n] < target) i++;
else { list[0] = i+1,list[1] = n+1;
return list;}
}
}
};
利用两边夹的方法来逐步锁定要找的两个元素,这个方法要比暴力遍历简单明了得多。
五.Find All Numbers Disappeared in an Array
Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.
Find all the elements of [1, n] inclusive that do not appear in this array.
Could you do it without extra space and in O(n) runtime? You may assume the returned list does not count as extra space.
Example:
Input:
[4,3,2,7,8,2,3,1]
Output:
[5,6]
这道题其实就是在找n个连续的数中哪些没有出现在长度为n的数组中(数字可重复)。
其实这里我的想法类似于第三题的排序。创建一个元素全为0新的数组,利用旧数组中已出现的数字作为下标使新数组中对应的元素为1,最后再来一次遍历找出仍为0的下标即可。
代码如下:
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> hashtable(nums.size()+1,0);
vector<int> list;
for(int i = 0; i < nums.size();i++)
{ hashtable[nums[i]]++;}
for(int i = 1; i<nums.size()+1;i++){if(hashtable[i] == 0) list.insert(list.end(),i);}
return list;
}
};
这一种做法的速度相比其他做法也算相当快了,因此我就不特别介绍其他更快的算法了。
六.Remove Duplicates from Sorted Array
Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length.
Do not allocate extra space for another array, you must do this in place with constant memory.
For example,
Given input array nums = [1,1,2],
Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn’t matter what you leave beyond the new length.
这道题有一个特殊之处,就是他需要一个修改后的数组,里面的数字属于原来的数组且互不相同。还有一个方便之处在于是在一个已经排好序的数组中找出n个不重复的数,也就是说,重复的数字都会贴在一起。我们做数组的题目都会从遍历开始想起,这一个特质无疑让我更加愿意去用遍历去完成初次AC。
这一种做法的核心就是当遇到第k个数与前一个数相等时,计数器+1。那么如何修改数组呢?很简单,当我发现开始有重复时,count计时器开始计算,那么我就要使重复的后面数字变为后面第一个与前面不同的数字。
代码如下:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int count = 0;
for(int i = 1; i < nums.size(); i++){
if(nums[i] == nums[i-1]) count++;
else nums[i - count] = nums[i]; //i- count是为了保证隔开count个重复的数字来进行修改
}
return nums.size()-count;
}
};
以上就是Week One的我的个人感想,下一次文章会考虑在解题的基础上讲一下Hash Table的构造和使用。