leetcode刷题记录(268、283、205)

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;
    }
}

执行后的时间和之前的差不多,但代码明显简洁了许多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值