2018.12.17 leetcode 刷题总结
题号: 268
给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
我的想法:
1.先将给定的数组排序;
2.遍历数组,但计数器的范围是从0到n,而不是0到数组的长度;
3.比较当前计数器的值和数组下标值为计数器的值时,数组元素的值;
4.若不相同,返回当前计数器的值,即为数组中缺失的值;
5.若全相同,n + 1;
对应程序:
// java
class Solution {
public int missingNumber(int[] nums) {
//参数检查
if(nums == null || nums.length == 0) {
return 0;
}
// 排序
Arrays.sort(nums);
int max = nums[nums.length - 1];
for(int i = 0; i < max; ++i) {
if(nums[i] != i) {
return i;
}
}
return max + 1;
}
}
这个方法虽然可以得到结果,但在leetcode中通过122个测试用例的时间有10ms,提交结果仅仅超过25.75%!
其他解法:
看看提交用时分布表中最高的提交记录:
// java
class Solution {
public int missingNumber(int[] nums) {
int val = 0;
for(int i = 0; i < nums.length; ++i) {
val += nums[i];
}
return (1 + nums.length) * nums.length / 2 - val;
}
}
看到return语句中的一长串计算表达式,放弃。。。是不可能放弃的,分析一下,看着像一个神秘的公式:
(1 + n)* n / 2 : (首项 + 末相)* 项数 / 2
求!和!公!式!
接着看,发现一个变量val,往上找它的定义,发现是表示所给数组中的全部元素的和,返回值就是这两个和的差值。
举个例子:
完整的数组:[0,1,2,3,4,5]
所给的数组:[0,1,2,3, 5]
差值正是数组中缺失的数字!!!
其实题目中还有这样一句话:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
所以,这道题不应该只考虑到比较的方法,而应该首先考虑成一个数学问题。
题号: 283
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
我的想法1:
1.得到数组中非零元素的个数num
2.将数组的前num项按顺序替换为数组中的非零元素
3.将数组中剩余的位置用0替代
对应程序1
// java
class Solution {
public void moveZeroes(int[] nums) {
// 记录不为零元素的个数
int num = 0;
for(int i = 0; i < nums.length; ++i) {
if(nums[i] != 0) {
num++;
}
}
int j = 0;
for(int i = 0; i < nums.length; ++i) {
if(nums[i] != 0) {
// 将不为零的元素按顺序写到数组的[0,num - 1]位置上
nums[j] = nums[i];
// 下标递增
j++;
}
}
for(int i = num; i < nums.length; ++i) {
// 用0填充数组下标为[num, nums.length]的元素
nums[i] = 0;
}
}
}
原本以为这个方法的执行速度不会很快,没想到通过21个用例测试的时间是2ms,超过96.79%的提交记录,还是不错的。
我的想法2:
利用双指针
1.一个一直指向数组中第一个为0的元素
2.另一个指向数组中第一个0元素之后的第一个不为0的元素
3.交换两个指针所指向的元素
对应程序2:
// java
class Solution {
public void moveZeroes(int[] nums) {
int zeroIndex = nums.length;
for(int i = 0; i < nums.length; ++i) {
if(nums[i] == 0) {
// 记录第一个元素为0的位置
zeroIndex = i;
}else {
// 不为0的元素要在为0元素的右边
if(zeroIndex < i) {
nums[zeroIndex] = nums[i];
nums[i] = 0;
// 从头开始
i = -1;
}
}
}
}
}
这个程序其实并没有严格按照我的想法去编写的,因为有一句:
i = -1
这让整个for循环重新开始,再次从头遍历了仅仅完成一次交换的新的数组,这相当于在做递归,是相当费时的,果然,在提交代码时,抛出了超时的警告,造成这次超时的用例:
[-1278640323,349172856,1873509219,2086212774,0,-1001344505,-61069976,746705870,-173131555,-1898820175,802998965,-916055673,344084770,-1664334387,-1886907515,171107295,-2065649057,-1628881728,-1900147601,-342880452,-2037018488,-104769833,0,0,374941067,536619007,-298730590,-1594869648,1855390876,904830187,-809158725,-1923547142,1340100626,953054962,1195315949,0,… 50744 more chars
(测试用例实在是太长了,我只截取了其中的一部分)
因此,我的这个双指针法想法是好的,但实现的不好。
其他解法:
// java
class Solution {
public void moveZeroes(int[] nums) {
// 指向第一个0元素
int zeroIndex = 0;
for(int i = 0; i < nums.length; ++i) {
if(nums[i] != 0) {
int temp = nums[i];
// 交换
nums[i] = nums[zeroIndex];
nums[zeroIndex] = temp;
// 下标右移
zeroIndex++;
}
}
}
}
上面才是双指针的正确用法
以上程序有几个巧妙的点:
1.若下标为0的元素不为0,在if中,zeroIndex和i指向的是相同元素,因此交换操作和不操作的结果相同,但 zeroIndex++ 会将指针右移,最终保证它可以指向0元素
2.交换元素和右移指针的操作可以保证右移后指针所指向的元素一定为0元素,且为第一个
优化
上面提到了:
若下标为0的元素不为0,在if中,zeroIndex和i指向的是相同元素,因此交换操作和不操作的结果相同
既然交换和不交换的结果相同,那么我们就加一个判断,当指向相同元素时就不做交换的操作:
...
if(zeroIndex == i){
//做交换
}
zeroIndex++;
...
题号:205
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身
示例 1:
输入: s = “egg”, t = “add”
输出: true
示例 2:
输入: s = “foo”, t = “bar”
输出: false
示例 3:
输入: s = “paper”, t = “title”
输出: true
说明:
你可以假设 s 和 t 具有相同的长度。
我的想法1:
1.根据第一个字符串得到一个通用的正则表达式
2.判断第二个字符串是否符合由1的得到的正则表达式
但我并没有写出这样的方法:将字符串转化为正则表达式
我的想法2:
1.定义两个Map
2.第一个Map的key是第一个字符串的字符,value是第二个字符串的字符
3.第二个Map的key是第二个字符串的字符,value是第一个字符串的字符
4.若两个Map的大小不相等,一定不是同构的字符串
5.若相同,继续比较第一个Map的key是否等于第二个Map的value,若不等,则一定不会是同构字符串
对应程序:
// java
class Solution {
public boolean isIsomorphic(String s, String t) {
// 参数检查
if((s == null && t == null) || ("".equals(s) && "".equals(t))) {
return true;
}
Map<Character, Character> stMap = new HashMap<>();
Map<Character, Character> tsMap = new HashMap<>();
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
for(int i = 0; i < sChars.length; ++i) {
stMap.put(sChars[i], tChars[i]);
tsMap.put(tChars[i], sChars[i]);
}
if(stMap.size() != tsMap.size()) {
return false;
}
for(int i = 0; i < stMap.size(); ++i) {
if(sChars[i] != tsMap.get(tChars[i])) {
return false;
}
}
return true;
}
这个程序要用到两个for循环才能最终确定结果,我个人感觉并不是很高效,看了看执行时间最短的方法,优化了一下自己的方法
优化
1.Map数量减少到一个
2.一边填充Map一边做判断
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character, Character> map = new HashMap<>();
for(int i = 0; i < s.length(); ++i) {
char sChar = s.charAt(i);
char tChar = t.charAt(i);
// 若sChar已经在Map中存在
if(map.containsKey(sChar)) {
// 判断key值为sChar的value和tChar是否相同
if(map.get(sChar) != tChar) {
return false;
}
}else {
// key不相同还要判断value
// 若key不同而value相同,一定不是同构
if(map.containsValue(tChar)) {
return false;
}else {
// key:第一个字符串中的字符
// value:第二个字符串的字符
map.put(sChar, tChar);
}
}
}
return true;
}
}
执行后的时间和之前的差不多,但代码明显简洁了许多。