鉴于最近是实习季,可能有很多小伙伴在刷leetcode,我把easy部分刷到的一些有趣的题和大家分享下,也是为了复习总结下,每道题都包含了我自己的做法和评论区 Most Votes的做法,有些做法真令人眼前一亮,大家可以参考下。
注:我题目的顺序是按通过率排序的,如果你想找特定的题,可以尝试ctrl+f搜索题号。
0.leetcode
教给我们最重要的就是一定一定一定要考虑特殊值情况,值为0?值为null?可不可能为负数?可不可能相加过程中产生溢出?不注意到这些细节问题,你几乎一道题也过不了。
283. move zeroes
题意如下
For example, given nums = [0, 1, 0, 3, 12]
, after calling your function, nums
should be [1, 3, 12, 0, 0]
.
不要笨笨的去交换,选择效率最大的方法:维护个指针把不是零的依次写入 然后后边全赋值为0,注意此操作就地就可以完成,不需要在申请O(N)的空间~。
102. binary tree level order traversal
二叉树的按层次遍历
一般树的问题都可以递归的去做,代码如下,自己看看就懂了。
1.递归
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void printbyOrder(TreeNode*root,int x,vector<int> &avector){
if (root==NULL) return;
else{
if (x==1) { avector.push_back(root->val);}
else{
printbyOrder(root->left,x-1,avector);
printbyOrder(root->right,x-1,avector);
}
}
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
for(int i=1;;i++){
vector<int> avector;
printbyOrder(root,i,avector);
if (avector.size()==0) break;
else{
result.push_back(avector);
}
}
return result;
}
2.用队列实现(为啥是c++???没错我就是喜欢在java c++ python随机挑个语言来写)
int i = 0;
while (!Q.empty()) {
i++;
int k = Q.size();
for (int j=0; j<k; j++)
{ TreeNode* rt = Q.front();
if (rt->left) Q.push(rt->left);
if (rt->right) Q.push(rt->right);
Q.pop();
if (rt->left==NULL && rt->right==NULL) return i; } //i为层次
}
9. Palindrome Number
题意为判断一个数字是否为回文数字,注意,将一个大的回文数首尾倒置可能会出现溢出的情况,该怎样处理这种问题呢?
1.我们最先想到用一个long来储存回文数,但显然这个方法有局限性,如果要处理的数字是long型?我们是否还能继续使其不溢出?
2.只判断一半,这个经过简单思考后也可以想出,大致代码如下。
public: bool isPalindrome(int x)
{ if(x<0|| (x!=0 &&x%10==0)) return false;
int sum=0; while(x>sum) //加个判断条件,只比较前半段和后半段
{ sum = sum*10+x%10; x = x/10; }
return (x==sum)||(x==sum/10); } };//奇数位或者偶数位
3.讨论区看到一个思路:溢出发生在sum = sum*10+x%10;
如果我们记录变化之前的sum值,然后把变化后的(sum-x%10)/10和之前的sum值比较,若不相等,则肯定溢出了。这个方法有一定的局限性,而且正确性也存疑,是否存在一个数他溢出后的值-x%10再除以10正好和原数相等?似乎不太好证明,如果你有兴趣可以自己探究下。
160.Intersection of Two Linked Lists
题意:求两个链表的交点
1.最垃圾的:俩链表双重循环找第一个相等的,O(N^2)
2.思考这种有交点链表特性:他们尾部都相同,头部一个比一个"长一点",我们可以计算出俩链表长度,设置俩游标在表头,然后预处理,将长链表的游标从首个前移动俩长度之差个节点,这样如果有交点,一定会移动到某处相等。代码如下:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
else{
int anum=0,bnum=0;
ListNode h=headA;
while(h!=null){
h=h.next;
anum++;
}
h=headB;
while(h!=null){
h=h.next;
bnum++;
}
int x=Math.abs(anum-bnum);
if (anum>=bnum){
ListNode p=headA;
ListNode q=headB;
for (int i=0;i<x;i++){
p=p.next;
}
while (p!=null&&q!=null){
if (p.equals(q)) return p;
p=p.next;
q=q.next;
}
return null;
}
else{
ListNode p=headA;
ListNode q=headB;
for (int i=0;i<x;i++){
q=q.next;
}
while (p!=null&&q!=null){
if (p.equals(q)) return p;
p=p.next;
q=q.next;
}
return null;
}
}
}
}
3.Discuss一个brilliant的解法:因为两个链表的和长度一定相等,所以把两个链表都从头开始,每次移动一个节点,移动到null就转为下一个链表的头,这样如果存在交点,一定会在某时相等。酷不酷?~~~~~~~~~~~~~·
88. Merge Sorted Array
设想在方法中新建个数组三,把排好序的放到数组三,再nums1=nums3?
行不通,java中是传引用,如果nums1[]实际是在栈中的一个地址,所以类似改变地址nums1=nums3的操作不能对原数组产生影响,但是改变数组内的值可以。
于是我们只能就地排序,(在nums1中),思考后发现只有倒序才有可能实现,正序会覆盖未排序的内容。很巧妙吧~
Like this
public class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int k=m+n-1;
m--;
n--;
while(m>=0 && n>=0){
if(nums1[m]>nums2[n]){
nums1[k--]=nums1[m--];
}
else{
nums1[k--]=nums2[n--];
}
}
while(n>=0){
nums1[k--]=nums2[n--];
}
}
}
36. Valid Sudoku
题意:判断是否为个数独合法状态
自己写的代码有点丑,学习下mostvotes中简洁的代码把。c++
sValidSudoku(vector<vector<char> > &board)
{ int used1[9][9] = {0}, used2[9][9] = {0}, used3[9][9] = {0};
for(int i = 0; i < board.size(); ++ i)
for(int j = 0; j < board[i].size(); ++ j)
if(board[i][j] != '.')
{jaint num = board[i][j] - '0' - 1, k = i / 3 * 3 + j / 3; //k未第几个小块
if(used1[i][num] || used2[j][num] || used3[k][num])
return false;
used1[i][num] = used2[j][num] = used3[k][num] = 1;
}
return true; //遍历完无重复
}
java hashsetpublic boolean isValidSudoku(char[][] board)
{ for(int i = 0; i<9; i++)
{
HashSet<Character> rows = new HashSet<Character>();
HashSet<Character> columns = new HashSet<Character>();
HashSet<Character> cube = new HashSet<Character>();
for (int j = 0; j < 9;j++)
{
//Hashset.add成功加入返回1,若已有元素返回0
if(board[i][j]!='.' && !rows.add(board[i][j])) return false;
if(board[j][i]!='.' && !columns.add(board[j][i])) return false;
int RowIndex = 3*(i/3); int ColIndex = 3*(i%3);
if(board[RowIndex + j/3][ColIndex + j%3]!='.' && !cube.add(board[RowIndex + j/3][ColIndex + j%3])) return false;
}
}
return true;
}
190. Reverse Bits
题意:反转一个数的二进制数
评论区一个天才的做法,供大家欣赏下
一个时间复杂度为O(log(sizeof(n)))的算法,采用了分治的方法
class Solution
{ public: uint32_t reverseBits(uint32_t n)
{ n = (n >> 16) | (n << 16);
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
return n; } };
eg.8位的,类似这样 abcdefgh -> efghabcd -> ghefcdab -> hgfedcba
219. Contains Duplicate II
Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = nums[j] and the difference between i and jis at most k.
My solution
用hashmap,如果便利过程中发现有重复的元素,检查map中value保存的index与当前差值是否小于k。
public class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
HashMap<Integer,Integer> a=new HashMap<Integer,Integer>(1+k);
for (int i=0;i<nums.length;i++){
if (a.get(nums[i])!=null){
if ((i-a.get(nums[i]))<=k)
return true;
else{
a.put(nums[i],i);
}
}
else{
a.put(nums[i],i);
}
}
return false;
}
}
Most votes solution
用hashset,手动维持一个大小为k的窗口
public boolean containsNearbyDuplicate(int[] nums, int k)
{ Set<Integer> set = new HashSet<Integer>();
for(int i = 0; i < nums.length; i++)
{
if(i > k) set.remove(nums[i-k-1]) //大于k时需要每次移除一个set中的值;
if(!set.add(nums[i])) return true; }
return false;
}
Fastest soluton?
先快速排序,然后判断,综合效率最快,处理数字序列问题时一定要想到排序~
https://leetcode.com/discuss/89129/5ms-98-8%25-java-solution
58. Length of Last Word
Mostvotes
简洁并且不调用其他函数的solution
int lengthOfLastWord(const char* s) {
int len = 0;
while (*s) {
if (*s++ != ' ')
++len;
else if (*s && *s != ' ')
len = 0;
}
return len;
}
len储存当前单词的长度,每次遇到一个新空格且没到字符串结尾就重置len,最后得到的就是最后单词的长度。203. Remove Linked List Elements
题意:删掉单链表中所有的值为val的元素
1.Mostvotes:
递归
public ListNode removeElements(ListNode head, int val)
{
if (head == null)
return null;
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
2.Mysoluton
即增加个虚拟链表头,此方法也适用其他边界处理较麻烦的问题!更简单些
public class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode hhead=new ListNode(0);
hhead.next=head;
ListNode pre=hhead;
ListNode now=head;
while(now!=null){
if (now.val==val){
pre.next=now.next;
now=now.next;
}
else{
pre=pre.next;
now=now.next;
}
}
return hhead.next;
}
14. Longest Common Prefix
题意:求一堆字符串最长前缀
Most votes的答案看起来效率并没有我的高,所以不贴了,还有一种做法先排序,然后比较第一个与最后一个,效率偏低。正常比较的复杂度应该是O(N+M)(M为第一个字符串长度)
public class Solution {
public String longestCommonPrefix(String[] strs) {
String result="";
int prefixnum=0;
if (strs.length==0) return result;
else{
result=strs[0];
prefixnum=result.length();
for(int i=1;i<strs.length;i++){
int j;
for(j=Math.min(prefixnum,strs[i].length());j>0;j--){
//如果前j字母相等,break
if (result.substring(0,j).equals(strs[i].substring(0,j))){
break;
}
}
prefixnum=j;
if (j==0) return "";
result=strs[i].substring(0,j);
}
return result;
}
}
}
257. Binary Tree Paths
1.传统递归做法:
public class Solution {
List<String> result;
public List<String> binaryTreePaths(TreeNode root) {
result=new ArrayList<String>();
if (root==null) return result;
else{
dfs(root,root.val+"");
return result;
}
}
public void dfs(TreeNode node,String s){
if (node==null) return;
else{
if (node.left==null && node.right==null) {
result.add(s);
return;
}
if (node.left!=null) dfs(node.left,s+"->"+node.left.val);
if (node.right!=null) dfs(node.right,s+"->"+node.right.val);
}
}
}
2.还可以用·栈来模拟dfs,用队列模拟bfs,相关python代码:
def binaryTreePaths1(self, root):
if not root:
return []
res, stack = [], [(root, "")]
while stack:
node, ls = stack.pop()
if not node.left and not node.right:
res.append(ls+str(node.val))
if node.right:
stack.append((node.right, ls+str(node.val)+"->"))
if node.left:
stack.append((node.left, ls+str(node.val)+"->"))
return res
# bfs + queue
def binaryTreePaths2(self, root):
if not root:
return []
res, queue = [], collections.deque([(root, "")])
while queue:
node, ls = queue.popleft()
if not node.left and not node.right:
res.append(ls+str(node.val))
if node.left:
queue.append((node.left, ls+str(node.val)+"->"))
if node.right:
queue.append((node.right, ls+str(node.val)+"->"))
return res
234. Palindrome Linked List
题意判断一个单链表是否回文,两个方法一个用快指针(一步前进两个),和慢指针找到链表的中点,然后把后半链表就地逆序,与前半部分相比较。
另一种按我的思路整个逆序一定要再新申请空间!否则原链表结构都被破坏,无法比较!
28. Implement strStr()
1.普通暴力搜索:
特别注意边界情况,当要查询的字符串为空时,返回0而不是-1;
for(int i=0;i<haystack.length();i++)
for(int j=0;j<needle.length();j++){
if (i+j>haystack.length()-1) return -1; //注意此句在比较是否相等前面
if (haystack.charAt(i+j)!=needle.charAt(j)) break;
if (j==needle.length()-1) return i;
}
return -1; //有可能主字符串为空
2.Kmp算法
Next 求法:仿照kmp算法,模式串和主串为同一个串,就是在key串中匹配他的前缀,注意初始化,i=1,j=0;匹配的过程中求下一个next[i+1]的值,如果next[1]到next[n] next[1]初始化为0,0为边界条件,用于判断。注意上段说的都是next[1]~next[n],需要加个前缀字符,否则next[0]可初始化为-1;
public class Solution {
public int strStr(String haystack, String needle) {
if (needle.length()==0) return 0;
if (needle.length()>haystack.length()) return -1;
int n=needle.length();
int[] next=new int[n+2];
int i=1;int j=0;
next[1]=0;
haystack="0"+haystack;
needle="0"+needle;
while(i<needle.length()-1){
if (needle.charAt(i)==needle.charAt(j)||j==0){
++i;
++j;
next[i]=j;
}
else{
j=next[j];
}
}
i=1;
j=1;
while(i<=haystack.length()-1&&j<=needle.length()-1){
if (haystack.charAt(i)==needle.charAt(j)||j==0){
i++;
j++;
}
else{
j=next[j];
}
}
if (j>needle.length()-1) return(i-j);
else return -1;
}
}
6. ZigZag Conversion
刷题心得,任何情况下都要考虑数组下标在某种情况下能否越界,尤其是边界情况!
1.MYsolution:
一行一行地加入,即算出没行的两次间隔,从主串中提取他们。
public class Solution {
public String convert(String s, int numRows) {
String[] result=new String[numRows];
for(int i=0;i<numRows;i++) result[i]="";
if (numRows==1) return s;
for(int j=0;j<numRows;j++){
int interval1=2*numRows-2*(j+1);
int interval2=(2*numRows-2)-interval1;
int start=j;
for(int i=0;;i++){
if(i%2==0) {
if (start>=s.length()) break;
if (interval1!=0){
result[j]=result[j]+s.charAt(start)+"";
start=start+interval1;
}
}
else{
if (start>=s.length()) break;
if (interval2!=0){
result[j]=result[j]+s.charAt(start)+"";
start=start+interval2;
}
}
}
}
String sb="";
for(int j=0;j<numRows;j++){
sb=sb+result[j];
}
return sb;
}
}
2.Mostvotes:
按z字形加入,先从上到下,再从下到上...
List<String> list=new ArrayList();
if(nums.length==1){
list.add(nums[0]+"");
return list;
}
for(int i=0;i<nums.length;i++){
int a=nums[i];
while(i+1<nums.length&&(nums[i+1]-nums[i])==1){
i++;
}
if(a!=nums[i]){
list.add(a+"->"+nums[i]);
}else{
list.add(a+"");
}
}
return list;
278. First Bad Version
二分法(head+tail)/2注意会不会溢出!!!!!!!!1
// 这样写二分更好,这样最后start=end=bad
public int firstBadVersion(int n) {
int start = 1, end = n;
while (start < end) {
int mid = start + (end-start) / 2;
if (!isBadVersion(mid)) start = mid + 1;
else end = mid;
}
return start;
}
1.TWO SUM
为了避免搜到自身,可以先搜targert-n(i),然后再加入hashmap,若存在答案,肯定能搜到
168. Excel Sheet Column Title
频繁操作字符串时,请用stringbuffer或者stringbuilder,不同之处,StringBuffer 是线程安全的,而 StringBuilder 则没有实现线程安全功能,所以性能略高。
StringBuilder str=new StringBuilder("");
str.append("jaewkjldfxmopzdm");
str.insert(i,",");
189. Rotate Array
java中array的方法:
从Array中创建ArrayList
String[] stringArray = { "a", "b", "c", "d", "e" };
ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(stringArray));
System.out.println(arrayList);
// [a, b, c, d, e]
连接两个数组
int[] intArray = { 1, 2, 3, 4, 5 };
int[] intArray2 = { 6, 7, 8, 9, 10 };
// Apache Commons Lang library
int[] combinedIntArray = ArrayUtils.addAll(intArray, intArray2);
数组翻转
int[] intArray = { 1, 2, 3, 4, 5 };
ArrayUtils.reverse(intArray);
System.out.println(Arrays.toString(intArray));
//[5, 4, 3, 2, 1]
依然用到了万能的ArrayUtils。
从数组中移除一个元素
int[] intArray = { 1, 2, 3, 4, 5 };
int[] removed = ArrayUtils.removeElement(intArray, 3);//create a new array
System.out.println(Arrays.toString(removed));
使用java数组要注意
思考这俩段代码为什么错误了:
public class Solution {
public void rotate(int[] nums, int k) {
int[] temp=new int[nums.length];
temp=nums; //这行只是传引用,也就是nums那段实际地址,既叫temp,又叫nums,你在修改nums数组值时,temp也会改变,循环出错,所以你要想拷贝一段一个相同数组请用for或者int copy[]=Arrays.copyof(a,a.length)方法,错误很隐蔽....
k=k%nums.length;
for(int i=0;i<nums.length;i++){
nums[(i+k)%nums.length]=temp[i]; //
}
}
}
public class Solution {
public void rotate(int[] nums, int k) {
int[] result=new int[nums.length];
k=k%nums.length;
for(int i=0;i<nums.length;i++){
result[(i+k)%nums.length]=nums[i];
}
nums=result; //java是传引用,出了这个方法后,num值并不会被改变。
}
}
165. Compare Version Numbers
注意java中Split中字符串为正则表达式,如果要匹配单引号双引号 用一个 \ 转义
如果要匹配点,需要用两个\\转义,首先字符串中的\\被编译器解释为\
然后作为正则表达式,\.又被正则表达式引擎解释为.
如果在字符串里只写\.的话,第一步就被直接解释为.,之后作为正则表达式被解释时就变成匹配任意字符了