HuanChen1025 / LeetCode-Record

LeetCode笔记

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

LeetCode-Record

LeetCode笔记

问题:

You are playing the following Nim Game with your friend: There is a heap of stones on the table, each time one of you take turns to remove 1 to 3 stones. The one who removes the last stone will be the winner. You will take the first turn to remove the stones.

Both of you are very clever and have optimal strategies for the game. Write a function to determine whether you can win the game given the number of stones in the heap.

For example, if there are 4 stones in the heap, then you will never win the game: no matter 1, 2, or 3 stones you remove, the last stone will always be removed by your friend.

大意:

你和你的一个朋友玩一个游戏:在一个桌子上有一堆石头,每个人每次可以从中拿1个或2个或3个石头,你和你朋友两个人轮流拿,谁拿走的桌上最后一波石头,谁就赢了。你先拿。 假设你和你的朋友都会采取最佳策略。写一个函数来判断对于给出的一个数量的石头,你会不会赢。 比如说,如果桌上有4个石头,那么你肯定输,无论你先拿1个还是2个还是3个,最后一波石头总是被你朋友拿走的。

思路:

乍一看好像不递归不可能判断出来,但其实演算一下,发现是有规律可行的。

  1. 首先当石头数量是3个以下时,你肯定会赢。
  2. 当石头数量为4个时,你肯定输。
  3. 当石头数量为5、6、7个时,你总是可以拿到桌上只剩4个,那对于你朋友来说,他面对4个石头,上面说了,一定会输,所以你一定会赢。
  4. 当石头数量是8个时,你不管怎么拿,最终桌上只会剩下5、6、7三种情况,那么此时你朋友面对的就是情况3,他就一定赢,而你一定输。
  5. 当石头数量是9、10、11时,你总是可以把石头拿到只剩8个,那么此时你朋友面对的就是情况4,那他就一定输,而你一定赢。 ……

这样分析一下,规律就出来了,只要你面对的石头数量是4的倍数,那你就一定会输,此外,你全都可以赢,赢面还是很大啊哈哈。代码也呼之欲出了。

代码(C++):

class Solution {
public:
    bool canWinNim(int n) {
        return n % 4 == 0 ? false : true;
    }
};

示例工程:https://github.com/Cloudox/NimGame

回到目录


问题:

Given a non-negative integer num, repeatedly add all its digits until the result has only one digit.

For example:

Given num = 38, the process is like: 3 + 8 = 11, 1 + 1 = 2. Since 2 has only one digit, return it.

Follow up: Could you do it without any loop/recursion in O(1) runtime?

大意:

给一个非负数,重复地加其中的数字知道最后只有一个数字。 比如说: 给出数字38,过程类似于:3+8=11,1+1=2.直到2只有一个数字了,就返回它。 继续: 你能不能不用任何循环、递归来在O(1)的时间复杂度中完成它?

###思路一: 首先想到的就是循环,对于一个数字,循环将其除以10的余数加起来,直到其是个位数。加完一次后判断是不是只有一个数字,也就是是不是小于10,如果还大于,说明还有多个数字,那就再进行同样的操作。这个方法我自己测试倒是对的,不过leetcode总是说我超时,而且是在11这个数时就超时。。

代码一(c++):

class Solution {
public:
    int addDigits(int num) {
        int tempResult = num;
		while (tempResult > 10) {
		    int tempNum = tempResult;
		    tempResult = 0;
       
	        for (; tempNum / 10 >= 1;) {
			    tempResult += tempNum % 10;
			    tempNum = tempNum / 10;
		    }
		    tempResult += tempNum;
	    }
		return tempResult;
    }
};

思路二:

既然题目里也鼓励我们继续思考不用循环的方式,那就一定是有规律可循。我们可以简单地列一下:

数字 结果
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 1
11 2
12 3
13 4
14 5
15 6
16 7
17 8
18 9
19 1
20 2
21 3

就列这么多了,我们可以发现,结果除了第一个0以外,都在19之间循环。而且可以发现,其除以9的余数,为0时,对应的结果是9,而不为0时,余数等于对应的结果,那么代码就呼之欲出啦

代码二(c++):

class Solution {
public:
    int addDigits(int num) {
        return num % 9 == 0 ? (num == 0 ? 0 : 9) : num % 9;
    }
};

示例工程:https://github.com/Cloudox/AddDigits

回到目录


问题:

Given a binary tree, find its maximum depth.

The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.

大意:

给出一个二叉树,找到其最大的深度。 最大深度是指从根节点到最远的叶子节点的最长距离的节点数。

###思路: 要探索二叉树的深度,用递归比较方便。我们题目要求的函数返回根节点的深度,那么就做到对二叉树上每个节点调用此函数都返回其作为根节点看待时的深度。比如,所有叶子节点的深度都是1,再往上就是2、3...一直到root根节点的返回值就是最大的深度。 对于每个节点,我们先判断其本身是否是节点,如果是一个空二叉树,那么就应该返回0。 然后,我们定义两个变量,一个左节点深度,一个右节点深度。我们分别判断其有无左节点和右节点,两种节点中的做法都是一样的,假设没有左节点,那么就左节点深度变量就是1,有左节点的话,左节点深度变量就是对左节点调用此函数返回的结果加1;对右节点也做同样的操作。 最后比较左节点深度和右节点深度,判断谁比较大,就返回哪个变量。这样就能一层一层地递归获取最大深度了。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public int maxDepth(TreeNode root) {
        if (root != null) {// 有此节点
	        int rightResult;
	        int leftResult;
	        if (root.left != null) {// 有左节点
				leftResult = maxDepth(root.left) + 1;
			} else {// 无左节点
				leftResult = 1;
			}
			if (root.right != null) {// 有右节点
				rightResult = maxDepth(root.right) + 1;
			} else {// 无右节点
				rightResult = 1;
			}
			// 判断哪边更深,返回更深的深度
            return leftResult > rightResult ? leftResult : rightResult;
        } else {// 无此节点,返回0
            return 0;
        }
    }
}

不过我们稍加思考一下,就可以进一步简略一下代码。因为我们代码里对于root为null的情况下返回的是0,那其实没有左节点时,对齐使用函数返回的也会是0,加1的话就是我们需要的1了,所以其实不用判断有无左节点,右节点也是一样。所以可以简化如下:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public int maxDepth(TreeNode root) {
        if (root != null) {
	        int rightResult = maxDepth(root.left) + 1;
	        int leftResult = maxDepth(root.right) + 1;
            return leftResult > rightResult ? leftResult : rightResult;
        } else {
            return 0;
        }
    }
}

回到目录


问题:

Invert a binary tree. to

Trivia: Google: 90% of our engineers use the software you wrote (Homebrew), >but you can’t invert a binary tree on a whiteboard so fuck off.

大意:

反转一个二叉树。 从

琐事: Google表示如果你连反转二叉树都做不到就滚吧。

思路:

对于二叉树的每个子节点的左右节点都要反转,我们还是用递归,对每个节点都调用函数,这样就都可以反转了。就同置换变量的数字一样,我们可以创建两个新的节点对象,然后分别等同于其左右子节点,然后将其左节点变成其右节点的新对象,将其右节点变成其左节点的新对象,就可以了。同时我们对每个子节点都要进行同样的操作。还有一点很重要不要忘记了,我们一开始要先判断此节点是否为null,不为null才进行操作。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {// 节点为空,不处理
            return null;
        } else {// 节点不为空
            TreeNode leftNode;// 左节点新对象
            TreeNode rightNode;// 右节点新对象
            if (root.left != null) {
                leftNode = invertTree(root.left);//对其子节点进行同样的操作,并赋给新对象
            } else {
                leftNode = null;
            }
            if (root.right != null) {
                rightNode = invertTree(root.right);//对其子节点进行同样的操作,并赋给新对象
            } else {
                rightNode = null;
            }
            // 反转左右子节点
            root.left = rightNode;
            root.right = leftNode;
            return root;
        }
    }
}

精简代码(Java):

我们在处理其子节点时,也判断了是否是null的情况,但其实我们函数中对本节点就会进行一次判断,也就是说如果子节点为null,那么函数本身就会返回null,用不着特意处理了。所以可以精简一下:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        } else {
            TreeNode leftNode = invertTree(root.left);
            TreeNode rightNode = invertTree(root.right);
            root.left = rightNode;
            root.right = leftNode;
            return root;
        }
    }
}

回到目录


题目:

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

For example, given nums = [0, 1, 0, 3, 12], after calling your function, nums should be [1, 3, 12, 0, 0].

Note: You must do this in-place without making a copy of the array. Minimize the total number of operations.

大意:

给出一个数字数组,写一个函数来移动其中的所有“0”到末尾,并保持其他非零元素的相对顺序不变。 比如说,给出数组 nums = [0, 1, 0, 3, 12],调用你的函数之后,nums应该变成[1, 3, 12, 0, 0]。 注意: 你必须在不复制数组的情况下做。 使操作数尽可能地少。

思路1:

首先想到了一个比较笨的方法,就是循环从头开始遍历数组中的每个数,遇到“0”,就将后面的所有数的位置往前移动一个,然后把最后一个数置为“0”,当进行完这样一次操作后,还要检测一下移动到前面来的下一位数是不是为“0”,如果是的话就再来一次同样的操作,否则就往下走。但是这样会遇到一个问题,那就是如果我后面的数都是“0”了,那我就会永远停留在某个位置循环,因为我移来移去当前位置的数都是“0”,所以在每次移动完后,就要检测一下后面的数是不是都是“0”了,只有当后面的数不都为“0”时,我才继续进行这种大挪移操作。

代码(Java):

public class Solution {
    public void moveZeroes(int[] nums) {
        int count = nums.length;
        for (int i = 0; i < count;) {
            if (nums[i] == 0) {// 当前数为0,进行大挪移
                for (int j = i; j < count - 1; j++) {
                    nums[j] = nums[j+1];
                }
                nums[count-1] = 0;
            }
            // 检测后面的数是不是都为0
            int is = 1;
            for (int j = i; j < count; j++) {
                if (nums[j] != 0) {
                    is = 0;
                    break;
                }
            }
            // 当前数不为0,或者后面都是0时,i++
            if (nums[i] != 0 || is == 1) i++;
        }
    }
}

这个代码的运行时间为25ms,明显有可以精简的地方,那就是当检测到后面的数字都是“0”时,就已经没必要再循环下去了,此时的数组已经符合要求了,直接结束就好,所以可以立马做出精简:

精简代码1(Java):

public class Solution {
    public void moveZeroes(int[] nums) {
        int count = nums.length;
        for (int i = 0; i < count;) {// 当前数是1,放到最后去,后面的数往前移
            if (nums[i] == 0) {
                for (int j = i; j < count - 1; j++) {
                    nums[j] = nums[j+1];
                }
                nums[count-1] = 0;
            } else {// 不是1
                i++;
            }
            // 检查后面的数是否都是0
            int is = 1;
            for (int j = i; j < count; j++) {
                if (nums[j] != 0) {
                    is = 0;
                    break;
                }
            }
            if (is == 1) return;// 后面都是0
        }
    }
}

这个代码的运行时间为23ms,减少了2ms,有一点效果,再观察一下,其实后面那个检查后面的数是否都为0的操作,明明可以放在那个移动数字的循环中去做,在移动数字时,同样也要对后面的所有数字进行操作,所以可以在同一个循环中进行,没必要循环两次,应该可以进一步缩减时间了,所以继续精简如下:

精简代码2(Java):

public class Solution {
    public void moveZeroes(int[] nums) {
        int count = nums.length;
        for (int i = 0; i < count;) {// 当前数是1,放到最后去,后面的数往前移
            if (nums[i] == 0) {
                int is = 1;// 标记是否后面的数都为0
                for (int j = i; j < count - 1; j++) {
                    nums[j] = nums[j+1];// 后面的数往前移
                    if (nums[j] != 0) is = 0;// 标记是有不为0的数
                }
                nums[count-1] = 0;
                if (is == 1) return;// 后面都是0,直接退出
            } else {// 不是1
                i++;
            }
        }
    }
}

这样一精简,运行时间反而变成了45ms,运行了几次基本都稳定在这个附近,这就无法理解了,明明应该缩减了一半的工作量,但时间反而加倍了,实在是无法想明白,请教一下大家这是为什么呢???

思路2:

之前那条路已经走不到了一个奇怪的境况中,而且感觉这种一下子移动一堆数字也不是个好办法,那么就思考另一种方法。我们可以只移动一个啊。还是从数组的第一个数开始循环,当发现“0”以后,立马在它后面找到第一个不为“0”的数字,然后交换这两个数字的位置,其余的数字都不用动,这样应该简单一些。同时,我们还是要在每次都检测后面的数字是否都为“0”,如果都为“0”了,那也没必要继续往下走了,可以直接结束。

代码2(Java):

public class Solution {
    public void moveZeroes(int[] nums) {
        int count = nums.length;
        for (int i = 0; i < count;) {// 当前数是1,放到最后去,后面的数往前移
            if (nums[i] == 0) {
                int is = 1;// 标记是否后面的数都为0
                for (int j = i; j < count; j++) {
                    if (nums[j] != 0) {// 找到后面第一个不为0的数
                        int value = nums[i];
                        nums[i] = nums[j];// 替换到前面来
                        nums[j] = value;
                        is = 0;// 标记后面有不为0的数
                        break;// 停止此次循环
                    }
                }
                if (is == 1) return;// 后面都是0,直接退出
            } else {// 不是1
                i++;
            }
        }
    }
}

这个代码的运行时间为18ms,就少了挺多了。

他山之石:

在Disguss中看到排名第一的答案,其代码如下:

public class Solution {
    public void moveZeroes(int[] nums) {
        if (nums == null || nums.length == 0) return;        

        int insertPos = 0;
        for (int num: nums) {
            if (num != 0) nums[insertPos++] = num;
        }        

        while (insertPos < nums.length) {
            nums[insertPos++] = 0;
        }
    }
}

这个代码就比较恐怖了,运行时间为0ms...完败啊。他的思路是:设置一个从0开始的标记,然后遍历每个数字,当数字不为“0”时,将nums数组的序号为标记的位置的数改成这个数,然后把标记加一,注意它的“++”是后置的,只有当检测到不为0的数字时,才会增加标记值,所以标记值永远小于等于我当前遍历到的数字的位置,就不会对其产生影响。当遍历完一次后,对标记值后面的位置的数,都置为0,这样就结束了。时间复杂度为O(n)。

回到目录


题目:

Write a function to delete a node (except the tail) in a singly linked list, given only access to that node.

Supposed the linked list is 1 -> 2 -> 3 -> 4 and you are given the third node with value 3, the linked list should become 1 -> 2 -> 4 after calling your function.

大意:

写一个函数来删除一个简单链表中的一个节点(除了尾节点),只给出这个节点。 假设有一个链表 1->2->3->4,并给你第三个值为3的节点,在调用你的函数之后这个链表应该变成1->2->4。

###思路: 一般我们删除一个链表节点,直接将其上一个节点的next指向其下一个节点就可以了,但是这里只给出了该节点本身,也就是说你只能获取到该节点本身以及其下一个节点。那么就只能将该节点直接变成下一个节点了,将其值设为下一个节点的值,将其next指向下一个节点的next,就可以了。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

代码(C++):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void deleteNode(ListNode node) {
        *node = *node->next;
    }
}

疑问:

用Java像C++那样,直接将 node = node.next 将不能ac,必须对其val和next分别设置,这是为什么呢?希望高手帮忙解答一下~

回到目录


题目:

Given two binary trees, write a function to check if they are equal or not.

Two binary trees are considered equal if they are structurally identical and the nodes have the same value.

大意:

给出两个二叉树,写一个函数来检查两者是否相等。 所谓相等,是指他们结构相同且节点有同样的值。

思路:

这个思路还比较直接,考虑全面一点就好了。首先考虑节点为空的情况,如果两个都为空,那么直接相等;如果一个为空一个不为空,那么不相等;如果两个都不为空,那么继续进行深层次的判断。 首先看两个节点的值是否相等,不相等则二叉树不等,然后判断其子节点,这时候使用递归就可以了,对两个节点的左节点和右节点分别调用这个函数,只有都返回相等时,才表示两个节点完全相同,由于递归,其子节点也就一层层地判断下去了,整个二叉树就会遍历完成。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            return true;
        } else if (p != null && q != null) {
            if (p.val != q.val) return false;
        
            if (isSameTree(p.left, q.left) && isSameTree(p.right, q.right)) return true;
            else return false;
        } else {
            return false;
        }
    }
}

其实还可以进一步精简代码,可以看下Discuss最火的代码,思路是一致的,只是精简到了极致,确实很赞:

精简代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
	    if(p == null && q == null) return true;
	    if(p == null || q == null) return false;
	    if(p.val == q.val)
	        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
	    return false;
    }
}

回到目录


题目:

Given two strings s and t, write a function to determine if t is an anagram of s.

For example, s = "anagram", t = "nagaram", return true. s = "rat", t = "car", return false.

Note: You may assume the string contains only lowercase alphabets.

Follow up: What if the inputs contain unicode characters? How would you adapt your solution to such case?

大意:

给出两个字符串s和t,写一个函数来判断t是否是s的易位构词。 比如说: s = "anagram", t = "nagaram", 返回true。 s = "rat", t = "car", 返回false。 注意: 你可以假设字符串只有小写字母组成。 进一步: 如果输入包含unicode字符呢?你如何调整你的代码来适应这种情况?

思路:

一开始,想了一个现在看来很笨的办法,这道题无非就是要检查两个字符串中的字母是否全部一致,我就遍历其中一个字符串,在每一个字符中,从另一个字符串找到第一个相同的字符,然后删掉字符串中的这个字符,继续遍历,直到有一个字符在另一个字符串中找不到了,说明没有这个字符或者数量少一些,就返回false,如果全部遍历完了都找得到,且另一个字符串也被删完了,那就返回true。这个办法我提交之后,很悲剧的超时了。。。想想也是,时间复杂度是n的平方了,还是很大的。 后来想到了另一个方法,我弄两个int数组,初始各自包含26个"0",用来记录两个字符串中各个字母出现的次数,然后分别遍历两个数组,记录其各个字母出现的次数,最后比较两个int数组是否完全一致就可以了,一遍ac,耗时5ms,打败了85%的提交者,哈哈哈。

代码(Java):

public class Solution {
    public boolean isAnagram(String s, String t) {
        // 用于存放两个字符串中字符出现次数的标记
        int[] sArray = new int[26];
        int[] tArray = new int[26];
        for (int i = 0; i < 26; i++) {
            sArray[i] = 0;
            tArray[i] = 0;
        }
        // 遍历两个字符串,记录字符出现次数
        char[] sCharArr = s.toCharArray();
        for (int i = 0; i < sCharArr.length; i++) {
            int index = sCharArr[i] - 97;
            sArray[index] += 1;
        }
        char[] tCharArr = t.toCharArray();
        for (int i = 0; i < tCharArr.length; i++) {
            int index = tCharArr[i] - 97;
            tArray[index] += 1;
        }
        // 比对两个记录字符出现次数的数组是否相等
        for (int i = 0; i < 26; i++) {
            if (sArray[i] != tArray[i]) return false;
        }
        return true;
    }
}

代码还是有点长,理所当然应该是可以精简的,然后我去看了看Discuss中最hot的答案,发现思路跟我是一样的,不过处理起来真的机智的不行:

精简代码(Java):

public class Solution {
    public boolean isAnagram(String s, String t) {
        int[] alphabet = new int[26];
        for (int i = 0; i < s.length(); i++) alphabet[s.charAt(i) - 'a']++;
        for (int i = 0; i < t.length(); i++) alphabet[t.charAt(i) - 'a']--;
        for (int i : alphabet) if (i != 0) return false;
        return true;
    }
}

其思路就是只有一个int数组,先遍历一个字符串,记录各个字母出现的次数,然后遍历另一个字符串,出现某个字母就将其对应的int数组中的值减一,最后判断int数组是否都是0,如果是,说明加减均衡,两个字符串一致,果然机智!

回到目录


题目:

Related to question Excel Sheet Column Title

Given a column title as appear in an Excel sheet, return its corresponding column number.

For example:

A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28

大意:

与题目Excel Sheet Column Title相关 给一个像Excel中显示的列标题,返回其对应的列数。 比如说: A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28

思路:

首先最简单的,AZ分别是126。然后AA到AZ是27~(26+26)。AAA到AAZ是2626+1 ~ 2626 + 26。 N位字母,前面位数的字母对应的数量总和为26^(n-1),可以总结出一个公式来。加上我们当前计算一个n位字母的列数,其前面位数的字母数量总和为26^(n-1),设其为startCount,从当前位数的字母开始计算,计算方法为:

startCount + ('A' - 65)*26^(n-1) + ('A' - 65)*26^(n-2) + ... + ('A' - 65) + 1

这样就可以总结为代码,分两步计算,第一步计算前面位数的字母数量总和,第二部计算当前位数的数量:

代码(Java):

public class Solution {
    public int titleToNumber(String s) {
        int count = 0;
        // 当前字母位数对应之前的数量
        int startCount = 0;
        for (int i = 1; i < s.length(); i++) {
            startCount += Math.pow(26, i);
        }
        
        // 加上前期数量
        count += startCount;
        count += 1;
        
        // 计算当前位数的数量:start + ('A' - 65)*26^(n-1) + ('A' - 65)*26^(n-2) + ... + ('A' - 65) + 1
        char[] sCharArr = s.toCharArray();
        for (int i = sCharArr.length - 1, j = 0; i >= 0; i--, j++) {
            count += (sCharArr[j] - 65) * Math.pow(26, i);
        }
        return count;
    }
}

他山之石:

最Hot的一个解决方法,只需要三行代码,把计算公式进行了化简,得出了一个特别简单的计算过程:

int result = 0;
for (int i = 0; i < s.length(); result = result * 26 + (s.charAt(i) - 'A' + 1), i++);
return result;

也是把代码行数节约到了极致。

回到目录


题目:

Given an array of integers, find if the array contains any duplicates. Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct.

大意:

给出一个int型的数组,判断数组是否包含了重复的数。如果有任何的数值在函数中出现过至少两次,你的数组就应该返回true,如果每个数值都是单一的,那么就返回false。

思路:

一开始我采用之前一个判断字母数的同样的思路,用一个10位的数组记录09的出现次数,后来运行说还有负数。。。于是加上了-9-1的9个数字,将数组改成19位,运行又发现还有极大的数。。。而不是我想的单一的个位数,这就超过数组的承受能力了,一开始又不说清楚= =。 于是换了一种思路,先将数组中的数字进行排序,排序之后数组中的内容就是按顺序排列的,如果有相同的数值,那一定是相邻排列的,所以只要遍历数组检查是否有相邻的两个数值相等就可以啦。这次终于ac了,看了一下Discuss的最Hot的方法,跟我的思路一样,太开心了。 关于排序有很多种方法,Java的数组自带有排序函数,也可以采用一些排序算法,可以参考这个博客:http://blog.csdn.net/fengyifei11228/article/details/2623980,写的还蛮全的。

代码(Java):

public class Solution {
    public boolean containsDuplicate(int[] nums) {
        Arrays.sort(nums);// 先数组内排序,参考:http://blog.csdn.net/fengyifei11228/article/details/2623980
        for (int i = 0; i < nums.length - 1; i++) {
            if (nums[i] == nums[i + 1]) return true;// 循环判断排序后有没有两个相同的数字
        }
        return false;
    }
}

回到目录


题目:

Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.

You may assume that the array is non-empty and the majority element always exist in the array.

大意:

给出一个尺寸为n的数组,找到主要的元素。所谓主要的元素是指出现次数超过n/2的元素。 你可以假设数组不为空且主要元素一定存在。

思路:

第一直觉是先排序把相同的元素都放到一起再说,因为主要元素的出现次数大于n/2,那么排序后最中间的元素一定是主要元素,不管怎么移动位置,最中间的都一定是它,所以可以很简单地完成代码啦。

代码(Java):

public class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);// 先排序
        return nums[nums.length/2];// 出现次数超过n/2次的元素排序后一定会出现在中间
    }
}

他山之石:

现在让我们看看Discuss最hot的答案,我的做法并不是最快的,因为排序需要时间,他说他的时间复杂度为O(1),看了一下代码,他的做法是:用一个变量记录主要元素,初始化为第一个元素,一个变量记录出现次数,初始化为1,遍历数组中的元素,与当前记录的主要元素相同的话,次数就加1,不同就减1,如果次数减到0,那就将主要元素换成新遍历到的元素,这样遍历完一轮得到最后记录的主要元素,就是我们要的结果。因为主要元素出现的次数大于n/2,所以可以想见最后留下来的一定会是主要元素。别的元素即使记录过也会因为次数归零抛弃掉的。这个方法只需要遍历一次数组就可以了,确实不容易想到。代码如下:

public class Solution {
    public int majorityElement(int[] num) {

        int major=num[0], count = 1;
        for(int i=1; i<num.length;i++){
            if(count==0){
                count++;
                major=num[i];
            }else if(major==num[i]){
                count++;
            }else count--;

        }
        return major;
    }
}

回到目录


题目:

Reverse a singly linked list.

大意:

反转一个简单链表。

思路:

题目的意思就是给出一个链表,本来是从头指向尾的,现在要你做成从尾指向头,并且返回原来的尾,现在的头。这个肯定是要用递归或者迭代来做。只要屡清楚过程,会比较绕。大体的流程就是,把下一个节点的next指向自己,一个个迭代、递归下去,最后返回最后的原来的尾节点

他山之石:

这里给出Discuss中最火的方法。 迭代实现:

public ListNode reverseList(ListNode head) {
    /* iterative solution */
    ListNode newHead = null;
    while (head != null) {
        ListNode next = head.next;
        head.next = newHead;
        newHead = head;
        head = next;
    }
    return newHead;
}

递归实现:

public ListNode reverseList(ListNode head) {
    /* recursive solution */
    return reverseListInt(head, null);
}

private ListNode reverseListInt(ListNode head, ListNode newHead) {
    if (head == null)
        return newHead;
    ListNode next = head.next;
    head.next = newHead;
    return reverseListInt(next, head);
}

回到目录


问题:

Write a function that takes a string as input and returns the string reversed.

Example:

Given s = "hello", return "olleh".

大意:

写一个函数获取输入的字符串然后返回反转后后的字符串。

比如:

给出s = "hello",返回"olleh"

思路:

思路很直接就想到,先把字符串拆分成一个个字符组成的数组,新建一个空字符串,然后从数组的最后一个字符往前遍历,每遍历一个都将其拼接到新字符处后面去,遍历完了就解决了。由于拼接的方式有很多,效率也各不相同,所以查了资料之后,选择了StringBuilder的方式,据说速度最快,但有线程安全的问题,而且只有JDK5支持。

###代码(Java):

public class Solution {
    public String reverseString(String s) {
        char[] sCharArr = s.toCharArray();// 拆分成数组
        StringBuilder sb = new StringBuilder();
        for (int i = sCharArr.length - 1; i >= 0; i--) {
	        // 遍历添加到末尾
            sb.append(sCharArr[i]);
        }
        return sb.toString();
    }
}

由于数据不够,也看不出我的速度比起别人到底如何,但我在项目中确实发现简单的用"+"来拼接字符串在量大了以后真的会非常慢,所以有其他方法的话还是尽量不要直接用"+"号了。

他山之石:

Discuss中看到一行代码解决的,也是用StringBuilder:

public class Solution {
    public String reverseString(String s) {
        return  new StringBuilder(s).reverse().toString();
    }
}

所以熟悉原生支持的方法真的很重要= =

C++:

其实C++的string本身就有一个reverse方法,接收开始和结束的迭代器,就可以做到反转了,但是提交后显示速度非常慢。

后来想到用copy配合rbegin、rend的反向迭代器来做,或者不要copy了,直接就返回一个反向迭代器构造出的字符串,看讨论中其他人也用过这种方法,但是可能是后来新增了示例,遇到有换行的字符串就会报错,尴尬。

代码(C++):

class Solution {
public:
    string reverseString(string s) {
        // 直接用reverse方法:
        return reverse(s.begin(), s.end());

        // copy搭配反向迭代器:
        // string res;
        // copy(s.rbegin(), s.rend(), res.begin());
        // return res;

        // 反向迭代器搭配构造函数:
        // return string(s.rbegin(), s.rend());
    }
};

回到目录


问题:

Calculate the sum of two integers a and b, but you are not allowed to use the operator + and -.

Example:

Given a = 1 and b = 2, return 3.

大意:

计算a和b两个整数的和,但是不能用+或-运算符。

比如:

给出 a = 1 和 b = 2,返回 3.

思路:

这道题乍看之下很简单,计算两个数之和嘛,但问题在于不能直接使用加号和减号,这就尴尬了,不过如果不这样,也称不上一道题了。其实对于运算,我们知道计算机本身就是没有什么加减乘除的,一切都是二进制在进行一些位运算,所以这里很显然的一个思路也就是转换成位运算,当然如果你本来就知道加法的实现原理,那也可以直接拿来做了。

我们先看个位数的二进制运算: 1 + 1 = 10; 1 + 0 = 1; 0 + 1 = 1; 0 + 0 = 0。

如果我们用^,也就是位异或运算来做: 1 ^ 1 = 0; 1 ^ 0 = 1; 0 ^ 1 = 1; 0 ^ 0 = 0。

其实观察可以看到,异或和直接的加唯一结果有区别的就是1+1这一条,但是换个想法,1+1是要进位的,进位后个位上还是变成0了,这样就一样了,只不过还需要有一个进位操作,而对于二进制,只有都是1的时候才会进位,这立马就可以联想到“与”操作了对不对: 1 & 1 = 1; 1 & 0 = 0; 0 & 1 = 0; 0 & 0 = 0。

既然是进位,我们当然要将得出来的结果进一位,这里使用左移运算符就可以了,所以对于1+1这种的做法就是异或加上与的进位: 1+1 = 1^1 + (1&1)<<1

当然我们还是不能有加号,所以对上那个加法我们还是要使用同样的方法,这就是递归了,那要到什么时候为止呢,从逻辑上来说,到不再有进位就可以了。

现在我们看一个二位数的加法:11+ 10 = 101

首先11^10 = 01, 然后11&10 = 10,左移一位得100, 现在有进位,那么继续 01+100, 01^100 = 101, 01&100 = 000, 这时候与操作后的结果为0了,可以停止运算了,最后的的结果应该是101,答案正确。说明思路是对的。

代码(Java)

public class Solution {
    public int getSum(int a, int b) {
        if (b == 0) return a;
        int sum,up;
        sum = a^b;
        up = (a&b)<<1;
        return getSum(sum, up);
    }
}

代码很简单,递归调用,在每次调用中都先检查进位的计算是不是为0了,也就是是不是没有进位了,如果没有了说明异或运算就是最终结果了,如果还有进位就继续算下去,算异或和与之后的左移然后继续调用。

看来这些看似理所当然的最简单的运算符,内里的门道也是需要了解清楚的。

回到目录


问题:

Given an integer, write a function to determine if it is a power of three.

Follow up: Could you do it without using any loop / recursion?

大意:

给出一个整数,写一个函数来判断它是否是3的次方数。 进阶: 你能不能不用循环和递归来做?

思路:

一开始看这个题目没明白是什么意思,后来查了一下才知道是判断是否3的次方数,所谓次方数就是n个3相乘得出的数咯,总是容易想到立方上去。这个题其实最简单的就是不断地除以3,直到结果为0,看有没有余数,有则不是,没有则是。这个做法无论是用循环还是递归都差不多,不过题目的进阶要求是不用循环与递归,这就要想办法了。找了会规律并没有找到,看了看别人的想法发现自己数学敏感性还是太差了,这直接可以转换成求对数的计算:

n = 3^x ⇒ log3(n) = x

要判断给出的整数是不是3的立方数,只用看x是不是整数就好了。而:

log3(n) = log10(n) / log10(3)

这里在做的时候我们直接用语言中的log函数去计算,但是要注意一点,必须要使用log10这个函数,而不能用ln或者其他数字做底数的log函数,否则在遇到243这个数字的时候会判断错误,我在这里也出了问题,这应该是一个底层计算中的巧合,在计算的时候:

log(243) = 5.493061443340548 log(3) = 1.0986122886681098 ==> log(243)/log(3) = 4.999999999999999

log10(243) = 2.385606273598312 log10(3) = 0.47712125471966244 ==> log10(243)/log10(3) = 5.0

由于我们的判断依据是log后的结果是否是一个整数,如果用其他数作为log的底数,那计算出来应该是整数的243结果却不是整数,因为计算机在计算log(3)时实际上结果会稍微大一点点,这就坑爹了,所以只能用log10。

要判断是不是整数很简单,直接%1看余数是不是0就好了。另外别忘了还有n=0的初始情况要考虑在内。再有值得一提的就是Math并不需要额外import就可以直接使用

代码(Java):

public class Solution {
    public boolean isPowerOfThree(int n) {
        return (n > 0 && (Math.log10(n) / Math.log10(3)) % 1 == 0);
    }
}

回到目录


问题:

Given an integer, write a function to determine if it is a power of two.

大意:

给出一个整数,写一个函数判断它是否是2的次方数。

思路:

这道题和另一道判断是否是3的次方数的题目很像,但是这个更简单,因为有一个二进制的东西存在,我们要判断一个数是不是2的次方数,不用去一次次除以2,也不用用log去算,直接转换成二进制,如果是2的次方数,那一定是最高位为1,其余位均为0的二进制数,所以只用判断这个二进制数是不是符合这个情况就可以了。 此外还有一个地方要小心,与判断3的次方数的题目描述有一点不同在于,这里没说给出的是非负数。。。所以一定还对负数的情况进行判断,很阴险。

代码(Java):

public class Solution {
    public boolean isPowerOfTwo(int n) {
        if (n < 0) return false;
        String binaryStr = Integer.toBinaryString(n);
        for (int i = 0; i < binaryStr.length(); i++) {
            if (i == 0 && binaryStr.charAt(i) != '1') return false;
            else if (i > 0 && binaryStr.charAt(i) != '0') return false;
        }
        return true;
    }
}

回到目录


问题:

Write an algorithm to determine if a number is "happy".

A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.

Example: 19 is a happy number

1^2 + 9^2 = 82 8^2 + 2^2 = 68 6^2 + 8^2 = 100 1^2 + 0^2 + 0^2 = 1

大意:

写一个算法来判断一个数字是否“happy”。 一个happy数字是通过下面的过程来判别的:从一个正整数开始,用其各位数字的平方和来代替它,然后重复这个过程直到数字等于1(此时就保持不变了),或者它会一直循环而不等于1。那些得到1的数字就是happy的数字。 距离:19是一个happy数字 1^2 + 9^2 = 82 8^2 + 2^2 = 68 6^2 + 8^2 = 100 1^2 + 0^2 + 0^2 = 1

思路:

一看到这个题目我是懵逼的,看一个数字是不是happy,出题人真有童心。想找规律吧算了几个数字感觉没得规律找啊。从最简单的思路来看就是不断循环看最后得到的是不是1了,但是返回true的判断容易,什么时候就可以下结论说这个数字不happy呢?这才是问题。首先我们得到的数不知道是几位数,但是经过计算后最后肯定会变成个位数,而如果这个个位数是1那就是happy了,如果不是1应该就是不happy吧。所以我一开始的做法是循环求平方和,直到结果是个位数了就看是不是1来给出结果,这里还用到了一个递归,如果计算一次平方和还不是个位数就继续递归计算。 提交后发现有个错误,那就是1111111这个数,也是一个happy数字,但我判断为不是了。我数了一下一共七个1,平方和是7,才知道原来到了个位数后还会继续计算,我算了一下发现7还真能最后算出1来,那只能对于19九个个位数都看看是不是能算出1来了,算了一下觉得太麻烦了,于是想到了一个简单的方法,leetcode是可以自定义测试用例的,勾选Custom Testcase就可以了,然后我把49都试了一遍,不用试2、3是因为就等于4、9,测完发现只有1和7是的,所以在代码里把7也算成true就可以了。 最后的时间是4ms,还不错,看了看discuss也没有看到特别好的方法,那大抵就是这样了吧。

代码(Java):

public class Solution {
    public boolean isHappy(int n) {
        int sum = 0;
        while (n > 0) {
            sum += Math.pow((n % 10), 2);
            n = n / 10;
        }
        if (sum >= 10) return isHappy(sum);
        else if (sum == 1 || sum == 7) return true;
        else return false;
    }
}

回到目录


问题:

Given a sorted linked list, delete all duplicates such that each element appear only once.

For example, Given 1->1->2, return 1->2. Given 1->1->2->3->3, return 1->2->3.

大意:

给出一个排好序的链表,删除所有的重复的数字,让每个元素只出现一次。 比如: 给出 1->1->2, 返回1->2. 给出1->1->2->3->3, 返回1->2->3.

思路:

既然链表本身已经排好序了,那么只用比较当前位置的值和next的值是否一样,一样就把next指向下一个再继续判断就好了,思路还是比较简单,但是有几个容易忽略的点需要注意。

  1. 首先是首节点为空的情况要考虑;
  2. 其次是只有当当前数字和下一个数字不一样时才把操作的节点换成下一个节点去继续向后操作,因为有可能有多个重复的数字串在一起,不能删除一个节点后就直接往后移进行判断,要判断删了一个以后下一个是否还是一样;
  3. 如果链表的最后几个数字都是重复的,我们在检测到重复的数字时会删除它然后将当前节点的next指向next的next,但是这里要注意判断next是否还有next,如果没有却进行操作,那就会出错了。

在自己检测时可以试试代码对下面几个测试用例是否能通过:

  • []
  • [1,1]
  • [1,1,2]
  • [1,1,1]

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) return head;
        ListNode p = head;
        while (p.next != null) {
            ListNode q = p.next;
            if (q.val == p.val) {
                if (q.next != null) {
                    p.next = q.next;
                }
                else p.next = null;
            } 
            else p = p.next;
        }
        return head;
    }
}

回到目录


问题:

Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also known as the Hamming weight).

For example, the 32-bit integer ’11' has binary representation 00000000000000000000000000001011, so the function should return 3.

大意:

写一个函数,获取一个无符号整型数并返回它拥有的‘1’bits的个数(也称为Hamming weight)。 比如,32位整型数‘11’二进制表示为00000000000000000000000000001011,所以函数应该返回3。

思路:

题目的意思其实就是问一个无符号整型数的二进制形式中有多少个1。这里无符号的意思是没有负数都是正数,直接的思路就是将它转换成二进制后一个个数里面1的个数,很简单。

代码(Java):

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        String binaryStr = Integer.toBinaryString(n);
        int result = 0;
        for (int i = 0; i < binaryStr.length(); i++) {
            if (binaryStr.charAt(i) == '1') result++;
        }
        return result;
    }
}

他山之石:

public static int hammingWeight(int n) {
	int ones = 0;
    	while(n!=0) {
    		ones = ones + (n & 1);
    		n = n>>>1;
    	}
    	return ones;
}

按照我的方法做完提交后虽然过了,也只有4ms,但是在结果统计中依然算慢的了,应该是转换成二进制那一步耗时了,然后一般数字都会有很多零,而我都会一个个判断一次,其实就不必要了。看了看Discuss中的好方法,确实挺好,直接让n和1去按位与,如果n的最后一位是1就会结果加一,然后将n右移一位继续判断,这里注意用到的右移运算发是三个箭头“>>>”,这是因为这是无符号数的右移运算符,有符号数就是两个箭头“>>”。同时循环的结束条件是n为0,这就免去了很多多余的判断,确实很赞。

回到目录


问题:

Write a program to check whether a given number is an ugly number.

Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 6, 8 are ugly while 14 is not ugly since it includes another prime factor 7.

Note that 1 is typically treated as an ugly number.

大意:

写一个程序来检查给出的数字是否是一个丑数字。 丑数字是指只能被2、3、5整除的正数。比如说,6、8是丑数字而14就不是丑数字因为它还有7这个约数。 注意1也被看待成丑数字。

思路:

leetcode中的题目都有有意思,一会happy数字一会ugly数字,也不知道是国外就这么叫的还是纯粹出题人有童心。这个题想着也没什么好办法,只能分别对2、3、5去看能不能被整除,能就除一下继续判断,直到不能被2、3、5整数了为止,看结果是不是1,是的话就是丑数字了,不是则不丑。看了看别人的做法,也大都是这个思路,这是实现上可能有点小差异。 还有一个要注意的是题目说丑数字是个正数,但没说给出的数字都是正数,在这里栽了个跟头,被0和负数弄得错了两次,我的Accepted率啊。。。

代码(Java):

public class Solution {
    public boolean isUgly(int num) {
        if (num <= 0) return false;
        while (true) {
            if (num % 2 == 0) {
                num = num / 2;
            } else if (num % 3 == 0) {
                num = num / 3;
            } else if (num % 5 == 0) {
                num = num / 5;
            } else if (num == 1) {
                return true;
            } else {
                return false;
            }
        }
    }
}

回到目录


问题:

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

大意:

你在爬一个楼梯。需要n节楼梯到达顶部。 每次你可以爬一节或者两节楼梯。你总共有多少种爬到顶部的方法?

思路:

总觉得这个题目小时候做过。一开始想着找找规律,按照全程走一个两节的、两个两节的、三个两节的这么去算,列了一下算式发现并没有什么规律。。。 后来想我每到一节都面临两个选择,即走一节还是走两节,于是想到用递归去做,不断返回在某一节为止走两节和走一节的走法数量之和。按照这种方法做了之后,一开始是能够解决的,测试到了44节楼梯的时候,就超时了,看了看答案给出的总走法数,确实是一个很大的数字,用递归要算的太多了。 既然往后面的走法去算走不通,那就往前看,我每来到一节新的位置的走法数量都是到上一节位置的走法数加上到上两节位置的走法数之和,一个是走一节楼梯到当前节,一个是走两节楼梯到当前节,这样从第二节开始去计算(第一节明显是一种走法,第二节开始才可以计算两节以前的位置走法数(即处于0位置时,设其为1)加上一节以前的位置走法数(即处于1位置时,设其为1)。)走到第二节的走法之和,慢慢算到最后一层。这里用一个数组去计算每一层的走法数。当然如果要节省空间也可以就用三个变量,只是每次去改变其值就可以了。

代码(Java):

public class Solution {
    public int climbStairs(int n) {
        int[] result = new int[n+1];
        result[0] = 1;
        result[1] = 1;
        for (int i = 2; i <= n; i++) {
            result[i] = result[i-1] + result[i-2];
        }
        return result[n];
    }
}

回到目录


问题:

Find the sum of all left leaves in a given binary tree.

Example:

 3
/ \
9  20
  /  \
 15   7

There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.

大意:

计算一个二叉树中所有左叶子节点的和

例子:

 3
/ \
9  20
  /  \
 15   7

在这个二叉树中有两个左叶子节点,分别为9和15。因此返回24。

思路:

从思路来说也没有什么特别的地方,就是去做判断,细心一点不要有漏洞就好。 大体上分为判断有没有左节点和有没有右节点。如果有左节点,看左节点有没有子节点,没有(即左叶子节点)则直接用起值去加,有则继续对左节点递归。如果有右节点,且右节点有子节点,则对右节点递归,否则不管是没有右节点还是右节点没有子节点(即右叶子节点)都直接看做加0。需要注意的是如果本身节点自己是null,要返回0。另外如果只有根节点自己,也要返回0,因为题目说的是左叶子节点,根节点是不算的。最后要注意的就是在判断所有节点的子节点或者值之前,要对该节点本身是否为null做出判断,否则会有错误的。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        if (root == null) return 0;
        else if (root.left == null && root.right == null) return 0;
        else {
            return ((root.left != null && root.left.left == null && root.left.right == null) ? root.left.val : sumOfLeftLeaves(root.left)) + ((root.right != null && (root.right.left != null || root.right.right != null)) ? sumOfLeftLeaves(root.right) : 0);
        }
    }
}

回到目录


问题:

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Example 1: Input: [7, 1, 5, 3, 6, 4] Output: 5

max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be larger than buying price) Example 2: Input: [7, 6, 4, 3, 1] Output: 0

In this case, no transaction is done, i.e. max profit = 0.

大意:

说你有一个由每天的股票价格组成的数组。 如果你只能进行一次交易(比如购买或者销售一个股票),设计一个算法来获取最大利润。 例子1: Input: [7, 1, 5, 3, 6, 4] Output: 5 最大的利润为:6-1 = 5(不是7-1 = 6,因为销售价格需要比购买价格大) 例子2: Input: [7, 6, 4, 3, 1] Output: 0 这个例子中,无法进行交易,所以最大收益为0。

思路:

这道题的意思就是,数组里的数按照顺序是每天的股票价格,你可以选择在某一天购买,然后再之后的某一天销售掉,销售的日子当然必须在购买的日子之后,也就是数组的顺序要靠后一些。要计算在哪两天进行买卖收益最大。这道题乍一看需要n*n的循环去计算每出一个价格后的最优解,我也是这样一开始就这样做,但是这样做时间复杂度太高,会超时,实际上也不用这么复杂。我们只需要每出一个新价格后,先判断这个价格减去我之前选择的买进价格后是否比之前的收益要大,以及这个新价格是否比我之前的买入价格要低,然后进行相应的操作即可,如果收益更大,就把这个收益记录下来,如果比买入价格低,就把买入价格换成这个价格。不需要担心这样直接换了之后往后计算的收益会不会不如之前,因为我们已经记录了在此之前的最大收益了,每次都会做对比的,而更换了更低的买入价格后,我们继续往后看能不能获得更大的收益,因为对于后面的数字来说这个数就是之前最小的买入价格了。其实想清楚后要进行的操作很简单,但如果想不清楚,就会觉得可能性太多了,要面面俱到总是会出问题,这里就要求头脑清晰了。

代码(Java):

public class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        int small = 0;
        if (prices.length == 0) return 0;
        else small = prices[0];
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] - small > result) result = prices[i] - small;
            if (prices[i] < small) small = prices[i];
        }
        return result;
    }
}

回到目录


问题:

Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.

According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”

    _______6______
   /              \
___2__          ___8__
/      \        /      \ 
0      _4       7       9
      /  \
      3   5

For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.

大意:

给出一个二叉查找树(BST),在其中找到给出的两个节点的最低的共同祖先(LCA)。

根据维基百科对LCA的定义:“最低共同祖先是指两个节点v和w在T中有v和w作为后代节点的最低节点(我们允许节点是自己的祖先)。”

    _______6______
   /              \
___2__          ___8__
/      \        /      \ 
0      _4       7       9
      /  \
      3   5

比如说,2和8的LCA是6。另一个例子,2和4的LCA是2,因为根据LCA的定义,一个节点可以是它自己的祖先。

思路:

这里要注意的地方是给出的二叉树是一个二叉查找树,所谓二叉查找树是指:

  1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 左、右子树也分别为二叉排序树;
  4. 没有键值相等的结点。

对于这个问题,如果是一个随意的二叉树要找LCA是比较麻烦的,要先找到目标节点的位置然后又反过来一层层找最低祖先。但是对于二叉查找树就要简单的多了,因为是排好序了的,可以简单地找到位置。

我们根据目标节点的值和根节点的值来判断目标节点在跟节点的左子树上还是右子树上,如果一个在左一个在右,就说明其LCA是根节点;如果都在左或者都在右,就对跟节点的左或者右子节点调用同样的方法进行递归。

因为没有键值相等的节点,所以判断时不用考虑等于的情况,这道题中也不需要考虑节点为null的特殊情况,所以代码很简单。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val - p.val > 0 && root.val - q.val > 0) return lowestCommonAncestor(root.left, p, q);
        else if (root.val - p.val < 0 && root.val - q.val < 0) return lowestCommonAncestor(root.right, p, q);
        else return root;
    }
}

回到目录


问题:

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

大意:

合并两个有序链表并返回一个新链表。新链表应该包含原先两个链表中全部节点。

思路:

合并两个有序的链表其实思路还挺直接的,从两个链表的第一个节点开始比较大小,将小的那个放到新链表里,然后后移继续比较,大概思路就是这样,只是实现起来麻烦一点。另外其实还想到了一个奇葩的思路,就是将两个链表中的键值放到一个数组中,对数组进行排序,然后将数组里的元素按顺序放到新建的节点里去然后放入一个新链表就可以了哈哈哈。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        else if (l2 == null) return l1;
        
        ListNode result;
        ListNode mark;
        
        if (l1.val <= l2.val) {
            result = l1;
            l1 = l1.next;
        } else {
            result = l2;
            l2 = l2.next;
        }
        mark = result;
        
        while (l1 != null) {
            if (l2 != null && l2.val < l1.val) {
                mark.next = l2;
                mark = mark.next;
                l2 = l2.next;
            } else {
                mark.next = l1;
                mark = mark.next;
                l1 = l1.next;
            }
        }
        if (l2 != null) mark.next = l2;
        return result;
    }
}

他山之石:

public ListNode mergeTwoLists(ListNode l1, ListNode l2){
		if(l1 == null) return l2;
		if(l2 == null) return l1;
		if(l1.val < l2.val){
			l1.next = mergeTwoLists(l1.next, l2);
			return l1;
		} else{
			l2.next = mergeTwoLists(l1, l2.next);
			return l2;
		}
}

这个方法的思路其实差不多,但是用的是递归的方式。耗时1ms,而上面我的代码耗时16ms,真的有这么大的差异么。。。

回到目录


问题:

Write a function that takes a string as input and reverse only the vowels of a string.

Example 1: Given s = "hello", return "holle".

Example 2: Given s = "leetcode", return "leotcede".

Note: The vowels does not include the letter "y".

大意:

写一个函数,输入一个字符串然后翻转里面的元音字母。

例1: 给出 s = "hello",返回"holle"。

例2: 给出 s = "leetcode",返回"leotcede"。

注意: 元音不包括字母“y”。

思路:

首先想到的一个思路是遍历字符串中每个字母,遇到元音字母就记录下字母和所在的位置。遍历完后,对着记录下来的元音字母,将字符串中的元音按照反序替换一遍就好了,这种做法也做出来了,但是结果非常耗时,花了200多ms。 后来想到了第二种方法,在字符串的头和尾都放一个指针进行遍历,两端向中间去遍历,当两端都遇到元音字母后,就对换。直到两个指针碰头为止。这个方法就快多了,同时优化一下检查是否是元音字母的方法,只需要几ms就搞定了。 需要注意的是题目中并没有说字符串是纯大写或者小写,所以大小写都要考虑,这个容易忽略。

代码1(Java):

public class Solution {
    public String reverseVowels(String s) {
        int[] vowelIndex = new int[s.length()];
        char[] vowelChar = new char[s.length()];
        int index = 0;// 标记上面两个数组记录的位置
        // 记录元音字母及出现的位置
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == 'a' || s.charAt(i) == 'e' || s.charAt(i) == 'i' || s.charAt(i) == 'o' || s.charAt(i) == 'u' || s.charAt(i) == 'A' || s.charAt(i) == 'E' || s.charAt(i) == 'I' || s.charAt(i) == 'O' || s.charAt(i) == 'U') {
                vowelChar[index] = s.charAt(i);
                vowelIndex[index] = i;
                index++;
            }
        }
        // 替换元音字母位置
        if (index == 0) return s;
        else {
            StringBuffer buffer = new StringBuffer(s);
            for (int i = 0; i < index; i++) {
                buffer.replace(vowelIndex[i], vowelIndex[i]+1, String.valueOf(vowelChar[index-i-1]));
            }
            return buffer.toString();
        }
    }
}

代码2(Java):

public class Solution {
	static final String vowels = "aeiouAEIOU";
	public String reverseVowels(String s) {
	    int first = 0, last = s.length() - 1;
	    char[] array = s.toCharArray();
	    while(first < last){
		    // 正向找元音
	        while(first < last && vowels.indexOf(array[first]) == -1){
	            first++;
	        }
	        // 逆向找元音
	        while(first < last && vowels.indexOf(array[last]) == -1){
	            last--;
	        }
	        // 对换
	        char temp = array[first];
	        array[first] = array[last];
	        array[last] = temp;
	        first++;
	        last--;
	    }
	    return new String(array);
	}
}

回到目录


问题:

Given an integer (signed 32 bits), write a function to check whether it is a power of 4.

Example: Given num = 16, return true. Given num = 5, return false.

Follow up: Could you solve it without loops/recursion?

大意:

给出一个数字(有符号32位),写一个函数来检查它是不是4的次方数。

例子: 给出 num = 16,返回true。给出 num = 15,返回false。

进阶:你能不能不用循环或者递归来解决它。

思路:

这道题也是老题目了,既可以用判断2的次方数的方法稍作修改,即转化为二进制数后判断1后面的0个数是不是双数。也可以直接用判断3的次方数的方法来做,直接求对数。

代码1(Java):

public class Solution {
    public boolean isPowerOfFour(int num) {
		// 转化为二进制数来判断
        if (num < 0) return false;
        String binaryStr = Integer.toBinaryString(num);
        for (int i = 0; i < binaryStr.length(); i++) {
            if (i == 0 && binaryStr.charAt(i) !='1') return false;
            else if (i > 0 && binaryStr.charAt(i) != '0') return false;
        }
        if (binaryStr.length() % 2 != 1) return false;
        return true;
    }
}

代码2(Java):

public class Solution {
    public boolean isPowerOfFour(int num) {
	    // 求对数
        return (num > 0 && (Math.log10(num) / Math.log10(4)) % 1 == 0);
    }
}

回到目录


问题:

Given a linked list, swap every two adjacent nodes and return its head.

For example, Given 1->2->3->4, you should return the list as 2->1->4->3.

Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed.

大意:

给出一个链表,交换每两个相邻的节点然后返回头节点。

例子: 给出 1->2->3->4,你应该返回链表 2->1->4->3。

你的算法应该只使用恒定的空间。你不能修改链表中的值,只有节点本身可以被改变。

思路:

题目里把最好用的一种方法禁止了,就是直接交换两个节点的值就可以了。但也还好做,就交换相邻节点的next指向的节点就可以了,然后递归下去,要注意判断节点是不是null的情况。不过这种做法一定要创建新的节点来临时存储节点,不知道这算不算不遵守题目要求呢。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head != null && head.next != null) {
            ListNode next = head.next;
            head.next = swapPairs(next.next);
            next.next = head;
            return next;
        } else return head;
    }
}

回到目录


问题:

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

大意:

你是一个专业的盗贼,计划偷一条街上的马。每匹马都带着明确数量的金钱,唯一阻止你偷所有马的原因是相邻的马都连接了安全系统,如果相邻的两匹马在同一天夜里被偷走就会自动联系警察。

给出一个非负数的数组表示每匹马所带的金钱数量,计算你今晚可以在不惊动警察的情况下偷到的最大金钱数。

思路:

面对这么一道题,感觉没什么思路,总觉得可能性太多了,不知道怎么去快速地判断计算,看了看Discuss的讨论,看到一个方法,想一想也有点道理,但又总觉得不确定是不是完全正确,试了试测试时通过了的,还是记录下来吧。

先考虑初始情况,如果没有马,则返回0,如果只有一匹马,那就直接偷了。如果有多匹马,那么对遇到的每匹马都进行一个判断:偷这匹马(为了不惊动警察所以不偷上一匹马)所得到的总金额和偷上一匹马(为了不惊动警察所以不偷这匹马)所得到的总金额哪个更大?哪个大就偷哪个,这样一直判断到最后一匹马。当然为了要达成这个判断就需要有两个变量来记录一些数据才能进行比较。这种判断就是在每两匹马之间判断哪个得到的效果更好,应该是对的吧。

代码(Java):

public class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        int last = 0;
        int now = 0;
        for (int i = 0; i < nums.length; i++) {
            int temp = last;
            last = now;
            now = Math.max(temp + nums[i], last);
        }
        return now;
    }
}

回到目录


问题:

Given a string which consists of lowercase or uppercase letters, find the length of the longest palindromes that can be built with those letters.

This is case sensitive, for example "Aa" is not considered a palindrome here.

Note: Assume the length of given string will not exceed 1,010.

Example:

Input: "abccccdd"

Output: 7

Explanation: One longest palindrome that can be built is "dccaccd", whose length is 7.

大意:

给出一个由小写或大写字母组成的字符串,找到能被其中的字母组成的最长的回文的长度。

这是区分大小写的,比如“Aa”就不能认为是回文。

注意: 假设给出的字符串长度不会超过1010。

例子:

输入: “abccccdd”

输出: 7

解释: 最长的回文为“dccaccd”,长度为7。

思路:

这里回文的意思就是正着和反着都是一样的字母顺序。思路大家都是比较一致的,先看看字符串有哪些字母以及各自的数量,把成双成对的数量的字母都挑出来,取其能成双的最大数量,这样可以对称地放在回文的两边,然后看有没有落单的字母或者在成双后还有剩余一个的字母,有就放在回文最中间,这样就是最长的回文了,数量也就出来了。这里是区分大小写的,所以要分开算。不过题目中的1010这个最长字符串长度没发现有什么特别的用处。

代码(Java):

public class Solution {
    public int longestPalindrome(String s) {
        int[] letterNum = new int[26*2];
        char[] sArray = s.toCharArray();
        for (int i = 0; i < sArray.length; i++) {
             if ((sArray[i] - 'a') >= 0) {// 小写字母
                 letterNum[(sArray[i] - 'a')]++;
             } else {// 大写字母
                 letterNum[(sArray[i] - 'A' + 26)]++;
             }
        }
        
        int result = 0;
        boolean hasSingle = false;// 有无单个字符的标记
        boolean hasOdd = false;// 有无2个以上奇数数量字符的标记
        for (int i = 0; i < 26*2; i++) {
            if (letterNum[i] > 0 && letterNum[i] < 2) hasSingle = true;
            if (letterNum[i] > 1) {
                result += letterNum[i] / 2 * 2;
                if (letterNum[i] % 2 == 1) hasOdd = true;
            }
        }
        result += hasSingle || hasOdd ? 1 : 0;
        return result;
    }
}

他山之石:

public int longestPalindrome(String s) {
        boolean[] map = new boolean[128];
        int len = 0;
        for (char c : s.toCharArray()) {
            map[c] = !map[c];         // flip on each occurrence, false when seen n*2 times
            if (!map[c]) len+=2;
        }
        if (len < s.length()) len++; // if more than len, atleast one single is present
        return len;
    }

这个做法其实思路是一样的,只是更加精简巧妙,boolan数组初始化时都是false,128的长度是为了包含所有ASCII码,懒得特意去判断大小写了,每次遇到一个字母就将其对应的位置取反,如果出现两次就会又变回false,那么每出现两次就可以将回文长度加2。最后看加起来的长度和原字符串长度是否相同,不同则说明有单个字符剩余,就可以放在回文正中间,跟我的做法比,思路差不多,代码却巧妙多了,厉害呀。

回到目录


问题:

Given two non-negative numbers num1 and num2 represented as string, return the sum of num1 and num2.

Note:

1、The length of both num1 and num2 is < 5100. 2、Both num1 and num2 contains only digits 0-9. 3、Both num1 and num2 does not contain any leading zero. 4、You must not use any built-in BigInteger library or convert the inputs to integer directly.

大意:

给出两个字符串形式的非负数num1和num2,返回num1和num2之和。

注意:

1、num1和num2的长度都小于5100。 2、num1和num2都只包含数字0-9。 3、num1和num2都不包含处于首位的0。 4、你不能使用任何内置的大数库或者直接将输入转化成整型。

思路:

题目不允许直接转化成整型去计算,也就是要我们一位一位地将数字加起来实现一次加法了。从两个字符串的最末尾开始去加,注意判断是否要进位,一位位加到两个字符串都遍历完为止,为了速度这里要使用StringBuilder,如果直接用 + 去进行字符拼接就太慢了,注意我们每次对每位数进行加时还是用整型来计算,这还是允许的,不然也太麻烦了,代码比较容易看懂。

代码(Java):

public class Solution {
    public String addStrings(String num1, String num2) {
        if (num1.length() == 0) return num2;
        else if (num2.length() == 0) return num1;
        
        boolean hasUp = false;// 是否进位
        int i = num1.length() - 1;
        int j = num2.length() - 1;
        StringBuilder sb = new StringBuilder();
        while (i >=0 || j >= 0) {
            int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
            int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
            int sum = n1 + n2 + (hasUp ? 1 : 0);
            if (sum >= 10) {
                sb.insert(0, Integer.toString(sum - 10));
                hasUp = true;
            } else {
                sb.insert(0, Integer.toString(sum));
                hasUp = false;
            }
            i--;
            j--;
        }
        if (hasUp) sb.insert(0, "1");
        return sb.toString();
    }
}

回到目录


问题:

Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left to right, level by level from leaf to root).

For example: Given binary tree [3,9,20,null,null,15,7],

3 /
9 20 /
15 7

return its bottom-up level order traversal as:

[ [15,7], [9,20], [3] ]

大意:

给出一个二叉树,返回从下到上的节点值序列。(比如,从左到右,一层层地从叶子到根)。

例子: 给出二叉树 [3,9,20,null,null,15,7],

返回从下到上的层级序列为:

思路:

这道题比较麻烦,要遍历二叉树,返回反过来顺序的二阶List。有两种方法,也就是经常说到的DFS深度优先遍历和BFS广度优先遍历。

BFS: 广度优先遍历就是一层层地攻略过去,把每一层的所有节点都记录下来再走向下一层。因为每层会有多个节点,不是简单的一个左节点一个右节点的,所以这里用到队列,用队列的先进先出特性来记录每一层的节点,保证对每层的每个节点都处理到其子节点,并将值记录下来。队列用到Queue这个类,offer方法可以添加一个元素,peek方法获取队首的元素,poll方法会从队首移除一个元素并获取它。

DFS: 深度优先遍历一般用递归来实现,也就是对每个方向都用递归来找到最底层的叶子节点,一层层处理回来,把每层的节点值添加到当前层的List中去。

代码:

BFS:

public class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        List<List<Integer>> wrapList = new LinkedList<List<Integer>>();
        
        if(root == null) return wrapList;
        
        queue.offer(root);
        while(!queue.isEmpty()){
            int levelNum = queue.size();
            List<Integer> subList = new LinkedList<Integer>();
            for(int i=0; i<levelNum; i++) {
                if(queue.peek().left != null) queue.offer(queue.peek().left);
                if(queue.peek().right != null) queue.offer(queue.peek().right);
                subList.add(queue.poll().val);
            }
            wrapList.add(0, subList);
        }
        return wrapList;
    }
}

DFS:

public class Solution {
        public List<List<Integer>> levelOrderBottom(TreeNode root) {
            List<List<Integer>> wrapList = new LinkedList<List<Integer>>();
            levelMaker(wrapList, root, 0);
            return wrapList;
        }
        
        public void levelMaker(List<List<Integer>> list, TreeNode root, int level) {
            if(root == null) return;
            if(level >= list.size()) {
                list.add(0, new LinkedList<Integer>());
            }
            levelMaker(list, root.left, level+1);
            levelMaker(list, root.right, level+1);
            list.get(list.size()-level-1).add(root.val);
        }
    }

回到目录


问题:

Given a linked list, determine if it has a cycle in it.

Follow up: Can you solve it without using extra space?

大意:

给出一个链表,判断它有没有回环。

进阶: 你能不能不用额外的空间来解决?

思路:

这道题我利用Java中Set集合的特性,即集合中不允许出现相同的对象。

我们遍历链表,边遍历边把每个节点放入一个Set集合中,并且每次都判断Set集合内对象的个数是否增加了,如果没增加,说明有相同的对象加入,也就是有回环了。

我看别人的解决办法,很多都是判断相邻的节点间有没有回环,其实不太理解,是不是回环只有相邻的这种情况呢,那如果存在大回环不是就判断不出来了么,还是觉得Set的方法比较保险。

代码(Java):

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) return false;
        HashSet list = new HashSet();
        list.add(head);
        int num = 1;
        while (head.next != null) {
            list.add(head.next);
            num++;
            if (num > list.size()) return true;
            head = head.next;
        }
        return false;
    }
}

回到目录


问题:

Given a non-negative number represented as an array of digits, plus one to the number.

The digits are stored such that the most significant digit is at the head of the list.

大意:

给出一个非负数的代表数字的数组,给这个“数字”加一。

这个数字的存储形式为最高位的数再数组的最前端。

思路:

但看题目还看不懂是什么意思,跑了几个测试用例后看答案看明白了,就是数组的每一个位置代表一个数的每一位,有个位十位百位等,如[8]表示数字8,[1,2]表示数字12等等,题目要求将给出的数字代表的数字加一并用同样的形式返回结果。

这个题目的要点在于对加法进位的判断,从个位数开始,如果加一后等于10,那么就要进位,而进位时也要不断对高位进行判断是否还要进位,所以只要把这个逻辑理清楚,代码还是好写出来的,我自己写出来的代码很复杂,明显可以进行优化。

后来看了下别人写的代码,思路差不多,也是判断进位,但是代码精简了许多,从最低位开始,如果不进位就直接返回,否则就按照要进位的方式去一遍遍循环,简单明了多了。

代码(Java):

public class Solution {
    public int[] plusOne(int[] digits) {
        int length = digits.length;
        int last = digits[length-1];
        last++;
        if (last < 10) {
            digits[length-1] = last;
        } else {// 最后一位要进位
            digits[length-1] = 0;// 最后一位记得置零
            int position = length-1;
            while (position >= 0) {
                if (position == 0) {// 只有一个数
                    // 在第0位插入一个数
                    int[] newDigits = new int[length+1];
                    newDigits[0] = 1;
                    for (int i = 0; i < length; i++) {
                        newDigits[i+1] = digits[i];
                    }
                    digits = newDigits;
                    break;
                } else {// 有多位数
                    position--;
                    int num = digits[position]+1;
                    if (num < 10) {
                        digits[position] = num;
                        break;
                    } else {// 要进位,置零后继续循环
                        digits[position] = 0;
                    }
                }
            }
        }
        return digits;
    }
}

他山之石:

public int[] plusOne(int[] digits) {
        
    int n = digits.length;
    for(int i=n-1; i>=0; i--) {
        if(digits[i] < 9) {
            digits[i]++;
            return digits;
        }
        
        digits[i] = 0;
    }
    
    int[] newNumber = new int [n+1];
    newNumber[0] = 1;
    
    return newNumber;
}

回到目录


问题:

Given an array and a value, remove all instances of that value in place and return the new length.

Do not allocate extra space for another array, you must do this in place with constant memory.

The order of elements can be changed. It doesn't matter what you leave beyond the new length.

大意:

给出一个数组和一个数,移除数组中所有等于该数的值并返回新长度。

不要使用额外的空间来创建另一个数组,你必须在恒定的内存中做。

元素的顺序可以改变。新长度以外的内容无所谓是什么样子。

思路:

这道题的要求在于不创建新数组,而是在原有的数组内容来进行操作,按题目的意思就是将所有不等于那个值的数放到数组前面,然后返回这些数的数量,也就是数组的一个长度,这个长度后面的数是什么样子的都没有关系。

按照这个思路很明显就是检测如果不一样就放到前面来了。我们弄两个指针,一个遍历数组中的数,一个记录不一样的数的数量,那么记录数量的数一定是小于等于遍历的那个指针数的,所以每当遍历到不一样的数时,就放在数组中记录不一样的数的位置,因为第二个指针一定是不会大于第一个数的,所以不会存在覆盖一些没遍历到的数的问题,这相当于是把不一样的数都集中到数组前面了,等遍历完了,后面的数其实还是保持原样的,跟题目要求的很契合。

代码(Java):

public class Solution {
    public int removeElement(int[] nums, int val) {
        int position = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != val) {
                nums[position] = nums[i];
                position++;
            }
        }
        return position;
    }
}

回到目录


问题:

Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).

For example, this binary tree [1,2,2,3,4,4,3] is symmetric:

But the following [1,2,2,null,3,null,3] is not:

Note: Bonus points if you could solve it both recursively and iteratively.

大意:

给出一个二叉树,检查它是否是自己的镜像(中心对称)。

比如,二进制数 [1,2,2,3,4,4,3] 是对称的

但 [1,2,2,null,3,null,3] 就不是:

注意: 如果可以用递归和迭代来做会加分

思路:

这道题我没想出来,总觉得要递归地去比较中间那么多数字是不是对称的不好做到,看了看别人的做法,还是很简单的,只能怪自己没想明白,想得太复杂了。

先检查root啊、左右子节点啊是不是null这些情况直接作出判断,然后用递归去做,每次检查一个节点的左子节点的左子节点和右子节点的右子节点以及左子节点的右子节点和右子节点的左子节点是不是一样的,就可以判断了。听起来有点绕,其实想一想就能明白了,我们总是去对一个节点的左右两个子节点去往下比较,这样就会越往下比较的越开,并不会出现单纯的相邻节点之间进行比较而已。

他山之石:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) return true;
        else return isMirror(root.left, root.right);
    }
    
    public boolean isMirror(TreeNode left, TreeNode right) {
        if (left == null && right == null) return true;
        else if (left == null || right == null) return false;
        else return (left.val == right.val) && isMirror(left.left, right.right) && isMirror(left.right, right.left);
    }
}

回到目录


问题:

Given numRows, generate the first numRows of Pascal's triangle.

For example, given numRows = 5, Return

大意:

给出一个行数,生出对应行数的杨辉三角形。

比如,给出行数 = 5。 返回

思路:

杨辉三角形好像是小学还是初中学的东西,像上面例子中显示的一样,每行数字递增1,,第一个数和最后一个数都是1,中间的每个数都是上一行对应位置和前一个位置的数之和。

这道题就是要根据给出的行数返回对应的杨辉三角形,那么也可以依据这个特性来做。每一行第一个数和最后一个数肯定都是1,中间的数根据上一行来计算,所以需要保存和更新每一个“上一行”,本行中间 j 位置的数,是上一行 j 位置和 j-1位置的数之和,这样一个个数,一行行计算出来就可以了。这道题要求结果放在ArrayList里,ArrayList经常用到,还是需要了解一下增删改查的用法。

代码(Java):

public class Solution {
    public List<List<Integer>> generate(int numRows) {
        if (numRows == 0) return new ArrayList<List<Integer>>();
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        List<Integer> lastArr = new ArrayList<Integer>();
        lastArr.add(1);
        result.add(lastArr);
        for (int i = 1; i < numRows; i++) {
            List<Integer> newArr = new ArrayList<Integer>();
            newArr.add(1);
            for (int j = 1; j < i; j++) {
                newArr.add(lastArr.get(j-1) + lastArr.get(j));
            }
            newArr.add(1);
            result.add(newArr);
            lastArr = newArr;
        }
        return result;
    }
}

回到目录


问题:

Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).

For example: Given binary tree [3,9,20,null,null,15,7],

3 /
9 20 /
15 7

return its level order traversal as:

[ [3], [9,20], [15,7] ]

大意:

给出一个二叉树,返回层级顺序的节点值(从左到右,一层层的)。

例子: 给出二叉树 [3,9,20,null,null,15,7]。

3 /
9 20 /
15 7

返回他的层级顺序为:

[ [3], [9,20], [15,7] ]

思路:

这道题和LeetCode笔记:107. Binary Tree Level Order Traversal II是姊妹题,解题思路都是一样的,只是结果要求的顺序是反的,同样有两种方法,也就是经常说到的DFS深度优先遍历和BFS广度优先遍历。

BFS: 广度优先遍历就是一层层地攻略过去,把每一层的所有节点都记录下来再走向下一层。因为每层会有多个节点,不是简单的一个左节点一个右节点的,所以这里用到队列,用队列的先进先出特性来记录每一层的节点,保证对每层的每个节点都处理到其子节点,并将值记录下来。队列用到Queue这个类,offer方法可以添加一个元素,peek方法获取队首的元素,poll方法会从队首移除一个元素并获取它。

DFS: 深度优先遍历一般用递归来实现,也就是对每个方向都用递归来往下找子节点,先用一个空的List占个位置,这一层每找到一个都添加到这个位置的List中去,一直找到最底层为止。

这个题目里BFS会比DFS快一点点。

代码(Java):

BFS:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        List<List<Integer>> result = new LinkedList<List<Integer>>();
        
        if (root == null) return result;
        
        queue.offer(root);
        while (!queue.isEmpty()) {
            int levelNum = queue.size();
            List<Integer> subList = new LinkedList<Integer>();
            for (int i = 0; i < levelNum; i++) {
                if (queue.peek().left != null) queue.offer(queue.peek().left);
                if (queue.peek().right != null) queue.offer(queue.peek().right);
                subList.add(queue.poll().val);
            }
            result.add(subList);
        }
        
        return result;
    }
}

DFS:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new LinkedList<List<Integer>>();
        levelMaker(result, root, 0);
        return result;
    }
    
    public void levelMaker(List<List<Integer>> list, TreeNode root, int level) {
        if (root == null) return;
        if (level >= list.size()) {
            list.add(level, new LinkedList<Integer>());
        }
        levelMaker(list, root.left, level+1);
        levelMaker(list, root.right, level+1);
        list.get(level).add(root.val);
    }
}

回到目录


问题:

Given a binary tree, determine if it is height-balanced.

For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

大意:

给出一个二叉树,判断它高度是不是平衡的。

对于这个问题,一个高度平衡的二叉树是指每个节点的两个子节点的深度的差异都不超过1的二叉树。

思路:

这道题题目说的太简略了也没有例子,根据不断试错摸出来的情况,就是说每个节点的两个子节点的深度不能想查大于1,比如左子节点下还有子节点,右子节点下没有了,这是允许的,深度相差1,但是如果左子节点的子节点还有子节点,深度相差就是2了,就不平衡了。

如果直接判断有无子节点,情况太多了不好做。不如直接递归记录每个节点的深度,然后比较每个节点的两个子节点的深度相差是否大于1。

这里要记录每个子节点的深度可以用递归的方法,叶子节点的深度为1,往上递增,注意在递归算一个节点深度时,要判断左子节点和右子节点的深度哪个更深,取更深的那一个,也就是数值更大的那一个。

然后可以再判断每个节点的左右子节点的深度相差是否大于1。但是要重新遍历一遍所有节点,这无疑增加了时间。在上面我们计算每个节点的深度的时候,有一步是判断左右子节点哪个节点更深,这里明显可以直接比较相差是否大于1,如果大于1,我们将该节点的深度特殊记为-1这个不会出现的深度值,我们希望可以传递到最顶层去告诉题目这个二叉树是不平衡的,因此在计算每个节点的左右子节点的时候,需要判断左右子节点的深度有没有等于-1的,如果有,说明下面有个地方出现不平衡了,这时候直接把-1这个信号往上传就好了。这样一层层递归传回root,就可以根据根节点的深度是否是-1来判断是否平衡了。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isBalanced(TreeNode root) {
        if (height(root) == -1) return false;
        else return true;
    }
    
    public int height(TreeNode root) {
        if (root == null) return 0;
        else {
            int leftHeight = height(root.left);
            if (leftHeight == -1) return -1;
            int rightHeight = height(root.right);
            if (rightHeight == -1) return -1;
            if (leftHeight - rightHeight > 1 || leftHeight - rightHeight < -1) return -1;
            return Math.max(height(root.left), height(root.right)) + 1;
        }
    }
}

回到目录


问题:

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.

大意:

给出一个有序数组,移除重复的数字,让每个元素只出现一次,并返回新的长度。

不要分配额外的空间给另一个数组,你必须在固定的内存下去做。

举例: 给出输入的数组为 nums = [1,1,2],

你的函数应该返回长度 = 2,并让前面两个数字为单独的1和2。在新长度之外的位置无所谓你留下了什么内容。

思路:

这道题和LeetCode笔记:27. Remove Element这个很类似,只不过这道题的难度在于,在操作中不能随意改变数组中原本元素的位置,因为你要保持它后面的数字还是有序的,才好去比较相邻的数字是否一样。其实也简单,我们弄一个变量记录上一个单独的数字,再弄一个变量记录该数字后面的位置序号,往后遍历,直到遇到不一样的数字,才把那个数字放到该位置序号来,然后把记录单独数字的变量设为这个新数字,记录位置的变量序号+1,继续往后遍历,因为是随着遍历过程往前放数字,所以不会影响到后面的数字顺序。

代码(Java):

public class Solution {
    public int removeDuplicates(int[] nums) {
        if (nums.length == 0) return 0;
        int position = 1;
        int lastNumber = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > lastNumber) {
                nums[position] = nums[i];
                position++;
                lastNumber = nums[i];
            }
        }
        return position;
    }
}

回到目录


问题:

Implement the following operations of a queue using stacks.

  • push(x) -- Push element x to the back of queue.
  • pop() -- Removes the element from in front of queue.
  • peek() -- Get the front element.
  • empty() -- Return whether the queue is empty.

Notes:

  • You must use only standard operations of a stack -- which means only push to top, peek/pop from top, size, and is empty operations are valid.
  • Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or deque (double-ended queue), as long as you use only standard operations of a stack.
  • You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue).

大意:

用堆栈实现一个满足下列操作的队列。

  • push(x)——push一个元素x到队列尾部。
  • pop()——从队列头部移除一个元素。
  • peek()——获取队列头部的元素。
  • empty()——返回队列是否是空的。

注意:

  • 你必须使用标准的堆栈操作——也就是只有push到顶端、从顶端peek/pop、size以及empty操作是有效的。
  • 根据你的语言,堆栈可能不是原生支持的。你可能要通过使用list或者deque(double-ended queue)模仿一个堆栈,就好像在使用标准的堆栈操作一样。
  • 你可以假设所有的操作都是有效的(比如不会对一个空队列进行pop或者peek操作)。

思路:

这道题要我们用堆栈来实现队列操作。堆栈和队列最大的区别就在于堆栈是先进后出的,而队列是先进先出的。所以在实现的时候,其他操作都好说,主要是pop和peek操作,我们需要将堆栈本身移除新加入的元素改为移除堆栈底部最开始加入的元素,要达到这个操作就得用另一个堆栈来临时存储数据,就像小时候玩的游戏,要先把堆栈里的数据全部倒到另一个堆栈里,才能取出最底部的元素,移除或者返回后,再将元素全部还原。

代码(Java):

class MyQueue {
    private Stack<Integer> in = new Stack<>();
    private Stack<Integer> out = new Stack<>();
    
    // Push element x to the back of queue.
    public void push(int x) {
        in.push(x);
    }

    // Removes the element from in front of queue.
    public void pop() {
        while (!in.empty()) {
            out.push(in.pop());
        }
        out.pop();
        while (!out.empty()) {
            in.push(out.pop());
        }
    }

    // Get the front element.
    public int peek() {
        while (!in.empty()) {
            out.push(in.pop());
        }
        int result = out.peek();
        while (!out.empty()) {
            in.push(out.pop());
        }
        return result;
    }

    // Return whether the queue is empty.
    public boolean empty() {
        return in.empty();
    }
}

他山之石:

class MyQueue {

    Stack<Integer> input = new Stack();
    Stack<Integer> output = new Stack();
    
    public void push(int x) {
        input.push(x);
    }

    public void pop() {
        peek();
        output.pop();
    }

    public int peek() {
        if (output.empty())
            while (!input.empty())
                output.push(input.pop());
        return output.peek();
    }

    public boolean empty() {
        return input.empty() && output.empty();
    }
}

这个做法其实和我的做法大致是一致的,但是我在进行pop和peek操作时,需要循环两次来进行倒出来又倒回去的工作。他的做法则是,只有当另一个堆栈空了,才将原堆栈的元素倒过去,因为每次倒都是全部倒空,新加入的元素会加到原堆栈去,而老元素都一批批地倒进另一个堆栈了,但我取元素的时候就是从老元素开始取得,所以只需要从另一个堆栈的顶部开始取就行了,毕竟倒一次顺序就反过来了,当取空了另一个堆栈后,就再倒一次,这样时间复杂度就大大降低了。

回到目录


问题:

Given an integer n, return the number of trailing zeroes in n!.

Note: Your solution should be in logarithmic time complexity.

大意:

给出一个整数n,返回n!后面0的个数。

注意:你的算法时间复杂度要是logn以内。

思路:

这道题的要求是计算n的阶乘后面0的个数,而且要求算法时间复杂度为logn,那么就绝对不是要人傻傻地做一遍阶乘再去做。

思考一下,什么时候末尾才会出现0,我们知道只有25,或者n10的时候才会在末尾出现0,其实10也可以看做一种25,那么其实就是看我们这个n中包含多少个25了,而因为有5就一定会有2,因为5比2大,相反有2不一定有5,所以只需要考虑n中有多少个5就可以了。此外,对于25这个数,我们知道25*4=100,末尾会有两个0,而4也比5小,所以当出现25时,会加上两个0,也就是说答案还要加上有多少个25。以此类推,还要考虑125、625等等,所以这是一个需要递归来实现的过程。

代码(Java):

public class Solution {
    public int trailingZeroes(int n) {
        return n == 0 ? 0 : n/5 + trailingZeroes(n/5);
    }
}

回到目录


问题:

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?

大意:

给出一个序号k,返回杨辉三角形的第k行。

比如给出 k = 3, 返回 [1,3,3,1]。

注意: 你能不能让你的算法只使用O(k)的额外空间?

思路:

这道题与LeetCode笔记:118. Pascal's Triangle很类似,那道题要求返回整个杨辉三角,这道题只要求一行,所以其实把那边的代码简化一下就可以了,不断利用杨辉三角的性质通过上一行计算下一行。

代码(Java):

public class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<Integer> last = new ArrayList<Integer>();
        last.add(1);
        for (int i = 1; i < rowIndex+1; i++) {
            List<Integer> newList = new ArrayList<Integer>();
            newList.add(1);
            for (int j = 1; j < i; j++) {
                newList.add(last.get(j-1) + last.get(j));
            }
            newList.add(1);
            last = newList;
        }
        return last;
    }
}

他山之石:

  public List<Integer> getRow(int rowIndex) {
	List<Integer> list = new ArrayList<Integer>();
	if (rowIndex < 0)
		return list;

	for (int i = 0; i < rowIndex + 1; i++) {
		list.add(0, 1);
		for (int j = 1; j < list.size() - 1; j++) {
			list.set(j, list.get(j) + list.get(j + 1));
		}
	}
	return list;
}

这个做法乍一看跟上面的做法很类似,但其实其精髓完全不一样,他这边只在一个List进行操作,每次循环的情况如下:

在0的位置添上1:[1],此时为第一行,不满足则继续; 不执行小循环,继续0的位置添上1:[1,1],此时为第二行,不满足则继续; 不执行小循环,继续0的位置添上1:[1,1,1],执行小循环,得[1,2,1],此时为第三行,不满足则继续; 此时继续0的位置添上1:[1,1,2,1],执行小循环,得[1,3,3,1],此时为第四行,不满足则继续; ……

这样下去确实可以得到每一行的数据,其实就是在一个List内模拟杨辉三角的性质,确实很赞。

回到目录


问题:

Write a program that outputs the string representation of numbers from 1 to n.

But for multiples of three it should output “Fizz” instead of the number and for the multiples of five output “Buzz”. For numbers which are multiples of both three and five output “FizzBuzz”.

Example:

n = 15,

Return: [ "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz" ]

大意:

写一个程序输出代表1到n的字符串。

但是3的倍数要输出“Fizz”,5的倍数要输出“Buzz”,3和5共同的倍数要输出“FizzBuzz”。

例子:

n = 15,

Return: [ "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz" ]

思路:

思路。。。这道题没啥思路好说的,无非就是在循环里判断是不是3和5的倍数并作出相应的处理罢了。就如Discuss里所说的,没有明白这道题的点在哪。实在不像leetcode的水平。

代码(Java):

public class Solution {
    public List<String> fizzBuzz(int n) {
        List<String> result = new ArrayList<String>();
        for (int i = 1; i <= n; i++) {
            if (i % 3 == 0 && i % 5 == 0) result.add("FizzBuzz");
            else if (i % 3 == 0) result.add("Fizz");
            else if (i % 5 == 0) result.add("Buzz");
            else result.add(String.valueOf(i));
        }
        return result;
    }
}

回到目录


问题:

You have a total of n coins that you want to form in a staircase shape, where every k-th row must have exactly k coins.

Given n, find the total number of full staircase rows that can be formed.

n is a non-negative integer and fits within the range of a 32-bit signed integer.

Example 1:

n = 5

The coins can form the following rows:

¤

¤ ¤

¤ ¤

Because the 3rd row is incomplete, we return 2.

Example 2:

n = 8

The coins can form the following rows:

¤

¤ ¤

¤ ¤ ¤

¤ ¤

Because the 4th row is incomplete, we return 3.

大意:

你有n枚硬币,想要用来组成一个完整的楼梯,每一层都要有层数对应的硬币数。

给出n,返回可以组成的完整楼梯的层数。

n是一个非负数,且满足32位int的范围。

例1:

n = 5

硬币可以组成下面的行:

¤

¤ ¤

¤ ¤

因为第三层是不完整的,所以我们返回2。

Example 2:

n = 8

硬币可以组成下面的行:

¤

¤ ¤

¤ ¤ ¤

¤ ¤

因为第四行是不完整的,所以我们返回3。

思路:

这道题要用硬币去一层层堆楼梯。其实很容易想到累加和。

题目的意思其实就是从1~x层完整楼梯硬币数量加起来,要小于等于n,求最大的x。说到加起来的数量,很容易想到求累加和,我们知道求累加和的公式为:

sum = (1+x)*x/2

这里就是要求 sum <= n 了。我们反过来求层数x。如果直接开方来求会存在错误,必须因式分解求得准确的x值:

(1+x)x/2 <= n x + xx <= 2n 4xx + 4x <= 8n (2x + 1)(2x + 1) - 1 <= 8n x <= (sqrt(8n + 1) - 1) / 2

其中Math.sqrt()是求平方根的函数。这样我们就求出了x,最后要记得强制转换为int型数。

代码(Java):

public class Solution {
    public int arrangeCoins(int n) {
        return (int)((Math.sqrt(8*(long)n + 1) - 1)/2);
    }
}

回到目录


问题:

You are given a binary tree in which each node contains an integer value.

Find the number of paths that sum to a given value.

The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).

The tree has no more than 1,000 nodes and the values are in the range -1,000,000 to 1,000,000.

Example:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

Return 3. The paths that sum to 8 are:

  1. 5 -> 3
  1. 5 -> 2 -> 1
  2. -3 -> 11

大意:

给你一个每个节点都包含int值的二叉树。

计算能累加成指定值的路径的个数。

路径不需要从根节点开始到叶子节点,但必须是往下走的(只能从父节点到子节点)。

树的节点数在1000以内,并且给定的值到-1000000到1000000之间。

例子:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

返回 3. 累加成8的路径数为:

  1. 5 -> 3
  1. 5 -> 2 -> 1
  2. -3 -> 11

思路:

这道题要从每个节点去判断往下走不断累加能不能达到要求的数字,是一个比较麻烦的过程,我们把它分解成两个过程:

  • 一个是对每个节点,都计算往下面不同分支走能不能累加成指定的值;
  • 另一个是从上往下去对每个节点进行上一条的判断。

要记录每一个节点,我们使用队列来进行节点的记录比较方便,队列的先进先出的,我们从根节点开始,将其加入队列中,判断能不能找到满足要求的路径,然后将其两个子节点加到队列中(当然要判断有无子节点),然后将根节点踢出队列,往后一次都是这个过程。

而对每个节点进行路径累加和的判断,用递归比较合适,这里要注意的是,一个节点往下走路径的过程中,并不是只要找到一条路径就可以了,而是要计算有多少条路径满足条件,也就是说对每个节点,找完左节点即使找到了,还要找右节点,一条路径满足后,如果对路径的最后一个节点还有子节点,那还要往下看看能不能继续累加满足,那又是一条新路径,因为有可能下面两个节点的值互相抵消了。所以在递归的过程中,函数返回的值不应该是能不能的布尔值,而是一个不断累加的数字,这个数字代表从一个节点往下走找到的路径数量。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public int pathSum(TreeNode root, int sum) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        int result = 0;
        
        if (root == null) return result;
        
        // 用队列来存储每次当做计算开头的每个节点
        queue.offer(root);
        while (!queue.isEmpty()) {
            int levelNum = queue.size();
            for (int i = 0; i < levelNum; i++) {// 计算队列中每个元素可否满足条件,并将子节点添加到队列
                result += canSum(queue.peek(), sum, 0);
                
                if (queue.peek().left != null) queue.offer(queue.peek().left);
                if (queue.peek().right != null) queue.offer(queue.peek().right);
                queue.poll();
            }
        }
        return result;
    }
    
    // 计算节点能达成条件的情况数量
    public int canSum(TreeNode root, int targetSum, int tempSum) {
        int sumNumber = 0;
        if (root == null) return sumNumber;
        
        if (root.val + tempSum == targetSum) sumNumber++;
        sumNumber += canSum(root.left, targetSum, root.val + tempSum) + canSum(root.right, targetSum, root.val + tempSum);
        return sumNumber;
    }
}

回到目录


问题:

Determine whether an integer is a palindrome. Do this without extra space.

大意:

判断一个整数是否是回文。不使用额外的空间来完成。

思路:

这道题目很简单,只有一句话,不要要求不使用额外空间,一般来说不使用额外空间的意思是不使用复杂度为O(n)的额外空间,新建一些字符串、整型值之类的还是可以的。回文的意思是从左到右读和从右到左读数字是一样的,比如11是回文,121是回文。

我们直接比较数字不太好比较(其实是打脸),先将其转为字符串,然后依次比较字符串第一位和最后一位、第二位和倒数第二位等等的字符是不是一样的,这里只需要比较到字符串长度一半的位置就可以了,原因显而易见。

题目比较蛋疼的设定是,题目中只说了整数,没说是正数,而他的答案判断负数统统不是回文,即使是-121这种也不行,一开始还直接取绝对值统一判断了。

代码(Java):

public class Solution {
    public boolean isPalindrome(int x) {
        if (x < 0) return false;
        String xStr = String.valueOf(x);
        for (int i = 0; i < xStr.length() / 2; i++) {
            if (xStr.charAt(i) != xStr.charAt(xStr.length()-i-1)) return false;
        }
        return true;
    }
}

他山之石:

public boolean isPalindrome(int x) {
    if (x<0 || (x!=0 && x%10==0)) return false;
    int rev = 0;
    while (x>rev){
    	rev = rev*10 + x%10;
    	x = x/10;
    }
    return (x==rev || x==rev/10);
}

这是直接用数字来做的一个做法,他有趣的一个想法是,只要数字是末尾为0的,也就是说除以10的余数为0,就一定不是回文,因为不可能最高位是0嘛。 然后他创建了一个整型变量来记录x从右往左读到一半时的数,而原来的x则一步步转化成从左往右读一半的数,最后看看两个数是不是相等,而因为有可能中间有单独一个数,所以还有可能是除以十以后相等。

回到目录


问题:

Given a binary tree, return all root-to-leaf paths.

For example, given the following binary tree:

All root-to-leaf paths are:

["1->2->5", "1->3"]

大意:

给出一个二叉树,返回所有从根节点到叶子节点的路径。

比如给出下面这个二叉树:

所有从根节点到叶子节点的路径为:

["1->2->5", "1->3"]

思路:

这道题适合用递归,依次判断有没有左右叶子节点,分别去做递归,在递归中把遇到的节点值拼接到路径字符串的最后,注意要拼接“->”这个内容,直到没有左右子节点后,表示已经到了叶子节点了,就可以终止了,把这条路径的字符串添加到结果中去。

代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result = new ArrayList<String>();
        if (root == null) return result;
        
        String path = String.valueOf(root.val);
        findPath(result, root, path);
        return result;
    }
    
    public void findPath(List<String> list, TreeNode root, String path) {
        if (root.left == null && root.right == null) {
            list.add(path);
            return;
        }
        if (root.left != null) {
            StringBuffer pathBuffer = new StringBuffer(path);
            pathBuffer.append("->");
            pathBuffer.append(String.valueOf(root.left.val));
            findPath(list, root.left, pathBuffer.toString());
        } 
        if (root.right != null) {
            StringBuffer pathBuffer = new StringBuffer(path);
            pathBuffer.append("->");
            pathBuffer.append(String.valueOf(root.right.val));
            findPath(list, root.right, pathBuffer.toString());
        }
    }
}

回到目录


问题:

Given a string s and a non-empty string p, find all the start indices of p's anagrams in s.

Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.

The order of output does not matter.

Example 1:

Input: s: "cbaebabacd" p: "abc"

Output: [0, 6]

Explanation: The substring with start index = 0 is "cba", which is an anagram of "abc". The substring with start index = 6 is "bac", which is an anagram of "abc".

Example 2:

Input: s: "abab" p: "ab"

Output: [0, 1, 2]

Explanation: The substring with start index = 0 is "ab", which is an anagram of "ab". The substring with start index = 1 is "ba", which is an anagram of "ab". The substring with start index = 2 is "ab", which is an anagram of "ab".

大意:

给出一个字符串s和一个非空的字符串p,找到p的重组字在s中出现的开始位置。

字符串全部由小写字母组成,s和p的长度都不超过20100。

输出的顺序无所谓。

例1:

输入: s: "cbaebabacd" p: "abc"

输出: [0, 6]

解释: “abc”的重组字“cba”可以从0开始找到。 “abc”的重组字“bac”可以从6开始找到。

例2:

输入: s: "abab" p: "ab"

输出: [0, 1, 2]

解释: “ab”的重组字“ab”可以从0开始找到。 “ab”的重组字“ba”可以从1开始找到。 “ab”的重组字“ab”可以从2开始找到。

思路:

这道题的意思就是给两个字符串,看p的顺序打乱后的所有可能的字符串在s中能不能找到,找得到就把所有找到的开始的位置记录下来。这个大概的思路要用到两个标记,去一点点比对p的重组字有没有可能找到,找不找得到这一点,不可能把p的所有可能的重组字先列出来,就只能一个字母一个字母地判断,如果用过了就去掉,看是全部字母都能找到还是只能找到部分。注意题目说了只有小写字母,而且p的长度不为空。我自己的做法在超长的测试用例时超时了,用的循环太多了。这里看别人非常精简巧妙的一个方法。

他山之石:

public List<Integer> findAnagrams(String s, String p) {
    List<Integer> list = new ArrayList<>();
    if (s == null || s.length() == 0 || p == null || p.length() == 0) return list;
    int[] hash = new int[256];
    for (char c : p.toCharArray()) {
        hash[c]++;
    }
    int left = 0, right = 0, count = p.length();
    while (right < s.length()) {
        if (hash[s.charAt(right++)]-- >= 1) count--; 
        
        if (count == 0) list.add(left);
    
        if (right - left == p.length() && hash[s.charAt(left++)]++ >= 0) count++;
    }
    return list;
}

这个代码非常精简,频繁地用到了后置++和--,这里要注意的是后置的计算是会先做玩判断后再进行加和减的,还有就是&&这个判断符,只有当前面的条件判断成功了才会去进行后面的判断,也就是说如果前面的不成立,后面的判断根本不会执行,这里也巧妙地利用了这个特性。这个代码可能过于精简了,来看一下稍微复杂化一点的同样的代码。

public class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> list = new ArrayList<>();
    if (s == null || s.length() == 0 || p == null || p.length() == 0) return list;
    
    int[] hash = new int[256]; 
    
    for (char c : p.toCharArray()) {
        hash[c]++;
    }
    
    int left = 0, right = 0, count = p.length();
    
    while (right < s.length()) {
        
        if (hash[s.charAt(right)] >= 1) {
            count--;
        }
        hash[s.charAt(right)]--;
        right++;
        
        if (count == 0) {
            list.add(left);
        }
        
        if (right - left == p.length() ) {
           
            if (hash[s.charAt(left)] >= 0) {
                count++;
            }
            hash[s.charAt(left)]++;
            left++;
        
        }

        
    }
        return list;
    }
}

这个算法首先是判断为空的情况,然后创建了一个数组用来存储p中的各个字符的数量,这是对于判断有无字母的一个很好的办法,先用每个字母位置的数量来表示各个字母的数量,接下来每次对各个字母的数量进行加减就可以了,这里的数组名hash只是一个数组,不要和哈希算法弄混了。

创建了左右两个标志位,一个用来表示判断字符串的起始位,一个表示终止位,都从0开始,还一个变量表示p的长度。

只要右标志位没有到s的最右边,就进行大循环。

对右标志位记录的s中的字母进行判断,看p中有没有,这里就是用那个表示p中字母数量的数组来进行判断的,找到了,就把表示要判断的字符串长度减一,不管有没有找到,都要把数量数组减少,右标志位右移,这是为了之后进行判断,因为我们要找的的字符串始终处于左和右的标志位的中间。

如果要找的字符串的长度减少到0了,说明我们在左右标志位中间找到了p字符串长度的重组字,这时候就可以把左标志位,也就是开始的位置,添加到结果数组中。

在循环的最后,先判断左右标志位中间是否是p的长度,是的话,我们就该把左标志位也右移了,而右移之前,先要看看左标志位这个数我们是否找到过,找到过则要把count数量补回1,不论有没有找到过,都要讲数组中的对应的字母数量补回1。

整个过程感觉理解的也不是很到位,希望大家指点一下。

回到目录


###问题:

We are playing the Guess Game. The game is as follows:

I pick a number from 1 to n. You have to guess which number I picked.

Every time you guess wrong, I'll tell you whether the number is higher or lower.

You call a pre-defined API guess(int num) which returns 3 possible results (-1, 1, or 0):

-1 : My number is lower 1 : My number is higher 0 : Congrats! You got it!

Example:

n = 10, I pick 6. Return 6.

###大意:

我们玩一个猜数字游戏,方法如下:

我在1到n之间选一个数字,你来猜我选的是什么

每次你猜错了,我都会告诉你数字是大了还是小了

你可以调用预定义的 API guess(int num) ,它会返回三个可能的结果 (-1, 1, or 0):

-1 : 我的数字更小 1 : 我的数字更大 0 : 恭喜!猜中了!

例子:

n = 10, 我选 6. 返回6.

###思路: 这道题题目主动提示了用二分法来做,所以只用把二分法的**写出来,根据每次猜测得到的大了或者小了的结果来进行分别处理。猜小了就在大的那个区间去继续取中间数字猜,猜大了就在小的那个区间取中间数字猜,因为取中间数字猜整体来说是最快的,为了记录区间,还要保留上次猜的情况,来让区间越缩越小。

###代码(Java):

/* The guess API is defined in the parent class GuessGame.
   @param num, your guess
   @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
      int guess(int num); */

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        return helper(1,n);
    }
    
    public int helper(int start, int end){
        if(start == end) return start;
        int mid = Math.toIntExact(((long)start+(long)end)/2);
        int r = 0;
        if(guess(mid) == 0) r = mid;
        else if(guess(mid) == 1) r = helper(mid+1, end);
        else if(guess(mid) == -1) r = helper(start, mid-1);
        return r;
    }
}

回到目录


###问题:

You are playing the following Bulls and Cows game with your friend: You write down a number and ask your friend to guess what the number is. Each time your friend makes a guess, you provide a hint that indicates how many digits in said guess match your secret number exactly in both digit and position (called "bulls") and how many digits match the secret number but locate in the wrong position (called "cows"). Your friend will use successive guesses and hints to eventually derive the secret number.

For example:

Secret number: "1807" Friend's guess: "7810"

Hint: 1 bull and 3 cows. (The bull is 8, the cows are 0, 1 and 7.)

Write a function to return a hint according to the secret number and friend's guess, use A to indicate the bulls and B to indicate the cows. In the above example, your function should return "1A3B".

Please note that both secret number and friend's guess may contain duplicate digits, for example:

Secret number: "1123" Friend's guess: "0111"

In this case, the 1st 1 in friend's guess is a bull, the 2nd or 3rd 1 is a cow, and your function should return "1A1B".

You may assume that the secret number and your friend's guess only contain digits, and their lengths are always equal.

###大意:

你和你的朋友玩下面这个Bulls and Cows的游戏:你写一个数字,然后问你的朋友去猜数字。每次你的朋友进行一次猜测,你都给他提示说有多少数字是数字与位置都正确的(成为bulls)以及多少数字是数字有但是位置不正确的(成为cows)。你的朋友会根据提示最终猜出数字来。

例子:

秘密数字:“1807” 朋友的猜测:“7810”

提示:1个bull以及3个cows。(bull是8,cows是0,1和7)

写一个函数来根据秘密数字和朋友的额猜测来返回暗示,使用A表示bull,B表示cows。在上面的例子中,你的函数应该返回“1A3B”。

请注意秘密数字和朋友的猜测都可能包含重复的数字,比如:

秘密数字:“1123” 朋友的猜测:“0111”

这种情况下,朋友猜测中的第一个1是bull,第二和第三个1是cow,你的函数应该返回“1A1B”。

你可以假设秘密数字和朋友的猜测都只包含数字,并且长度相等。

###思路: 这道题分两步:

第一步找到bull,也就是数字和位置都正确的个数,这个直接循环比对两个字符串同等位置的数字是否一样就好,为了方便我们先全部转换成数组去比较,比较完了记录下个数,还要记录下有哪些位置的数字是bull,这样第二步找cow的时候就不要再判断了。

第二步找cow,再次循环朋友的猜测,这次我们要跳过那些是bull的位置,对不是bull的每一个数字,去循环秘密数字中的数进行判断,判断时要注意第一不能位置一样,第二数字要相等,第三不能是秘密数字中已经是bull的位置。且找到以后除了增加cow数字外也要记录下来在秘密数字中的位置,以后就不要再找了。

在第二次找的过程中要注意我们不能重复找秘密数字中的位置,也就是有一个新数组要记录秘密数字中已经被找到的数字,这时候和bull中的位置是不一样的,所以要另外加一个数组,但是加的时候不能直接等于之前第一步记录的数组来创建,这样在进行修改数组的时候其实还是修改的第一个数组,因为只是引用了一遍位置,要进行深复制,使用clone。

此外对于朋友猜测中的一个数组,只能找一个cow,不能循环找多个,所以找到一个以后直接break退出循环开始找下一个数字。

###代码(Java):

public class Solution {
    public String getHint(String secret, String guess) {
        char[] secretArr = secret.toCharArray();
        char[] guessArr = guess.toCharArray();
        int[] bullArr = new int[guess.length()];
        int bull = 0;
        int cow = 0;
        for (int i = 0; i < guessArr.length; i++) {
            if (guessArr[i] == secretArr[i]) {
                bull ++;
                bullArr[i] = 1;
            }
        }
        int[] bullSecretArr = bullArr.clone();
        for (int i = 0; i < guessArr.length; i++) {
            if (bullArr[i] == 0) {
                for (int j = 0; j < secretArr.length; j++) {
                    if (i != j && guessArr[i] == secretArr[j] && bullSecretArr[j] == 0) {
                        cow++;
                        bullSecretArr[j] = 1;
                        break;
                    }
                }
            }
        }
        String result = String.valueOf(bull) + 'A' + String.valueOf(cow) + 'B';
        return result;
    }
}

###他山之石:

public class Solution {
    public String getHint(String secret, String guess) {
        int[] a1 = new int[256];
        int[] a2 = new int[256];
        
        int count1 = 0, count2 = 0;
        
        for(int i = 0; i < secret.length(); i++){
            if(secret.charAt(i) == guess.charAt(i)) count1++;
            else{
                a1[secret.charAt(i)]++;
                a2[guess.charAt(i)]++;
            }
        }
        
        for(int i = 0; i < 255; i++){
            if(a1[i] == a2[i]) count2+=a1[i];
            else count2 += Math.min(a1[i],a2[i]);
        }
        
        return count1+"A"+count2+"B";
    }
}

这个做法第一步也是直接找bull,巧妙的地方在于第二步找cow数量的方法。在第一步中对于不是bull的位置的数字,分别都记录下来对应位置的数字,对数字的数量进行累加,这样循环一遍后就知道各自还有哪些数字没找到,以及他们的个数。在找cow的时候,只需要对每个出现过的数字,取两边出现的较少的那一个数量即可,这样也可以避免重复,很巧妙地减少了时间复杂度。

回到目录


###问题:

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

For example: Given the below binary tree and sum = 22,

return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.

###大意:

给出一个二叉树和一个值,判断树是否有从根节点到叶子节点的路径让每个节点的值加起来等于给出的值。

例子: 给出下面的二叉树以及 sum = 22,

返回true,因为存在根节点到叶子节点的路径 5->4->11->2 加起来的和为22。

###思路: 这个因为只需要判断有没有路径满足,也就是说只需要找到一条即可,那么采用深度优先遍历比较好,用递归来实现。

每次判断当前路径的累加和是否等于目标值了,如果等于,因为题目要求从根节点到叶子节点,所以还要判断是否已经到叶子节点了,这个对有无左右子节点判断就可以了。

如果还不等于,那么就继续判断走左子节点或者走右子节点有没有等于。

要注意的是题目并没说节点值都是正数,我之前对当前的累加和是否已经大于了目标值来希望减少一些多余的运算,属于自作聪明了,对于负数目标值来说,就完全错误了。

###代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        return canSum(root, sum, 0);
    }
    
    public boolean canSum(TreeNode root, int sum, int nowSum) {
        if (root == null) return false;
        
        nowSum = nowSum + root.val;
        System.out.println(nowSum);
        if (nowSum == sum && root.left == null && root.right == null) return true;
        else return canSum(root.left, sum, nowSum) || canSum(root.right, sum, nowSum);
    }
}

回到目录


###问题:

Given two strings s and t, determine if they are isomorphic.

Two strings are isomorphic if the characters in s can be replaced to get t.

All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.

For example, Given "egg", "add", return true.

Given "foo", "bar", return false.

Given "paper", "title", return true.

Note: You may assume both s and t have the same length.

###大意:

给出两个字符串s和t,判断他们是不是同构的。

如果s中的字母可以被t中的替换,就说两个字符串是同构的。

所有出现的字母都必须被其他的字母以原来的顺序去替换。两个字母不同替换同一个字母,但是一个字母可以替换他自己。

例子: 给出 “egg”,“add”,返回true。

给出“foo”,“bar”,返回faalse。

给出“paper”,“title”,返回true。

注意: 你可以假设s和t的长度是一样的。

###思路: 这道题的意思是把原来的字符串字母替换掉,出现了几次的字母,替换他的也要出现几次,都要在原来的位置出现。

说到字母还是想到用记数字的方式来判断,遍历字符串记录每个字母出现的次数,因为没说只有小写,所以记录次数的数组容量要大一点,因为说了两个字符串长度一样,所以在一个循环里面遍历就可以了。

记录完后再遍历一次字符串,对两个字符串中依次出现的对应位置的字母判断其出现次数是不是一样的。

不过还有一个问题,提交时遇到一个测试用例为“abba”与“abab”,这个用例中字母出现的次数是一样的,但是位置有点差异,要解决它,就得再创建两个数组记录两个字符串中对应字母出现的位置的序号之和,只有对应字母出现的位置序号的和也是一样的,才能保证是完全同构的。

###代码(Java):

public class Solution {
    public boolean isIsomorphic(String s, String t) {
        int[] sNum = new int[128];
        int[] sPosition = new int[128];
        int[] tNum = new int[128];
        int[] tPosition = new int[128];
        for (int i = 0; i < s.length(); i++) {
            sNum[s.charAt(i)]++;
            sPosition[s.charAt(i)] += i;
            tNum[t.charAt(i)]++;
            tPosition[t.charAt(i)] += i;
        }
        
        for (int i = 0; i < s.length(); i++) {
            if (sNum[s.charAt(i)] != tNum[t.charAt(i)]) return false;
            if (sPosition[s.charAt(i)] != tPosition[t.charAt(i)]) return false;
        }
        return true;
    }
}

回到目录


###问题:

Given a binary tree, find its minimum depth.

The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.

###大意:

给出一个二叉树,找到他最小的深度。

最小的深度是指从根节点到叶子节点距离最短的节点数。

###思路: 这道题要找到最短的深度,个人认为更应该用广度优先遍历来做,一层一层地扫过去,哪一层有叶子节点了,就不扫了,把深度返回来。如果用深度优先遍历,每一条路都要走完,要所有路径都走到最后才能确定最短的。

广度优先取一个队列来记录每一层的节点数,利用队列先进先出的特性,一层层扫,每次扫到下一层的都加到队尾,把当前层的个数扫完后就将层数加一,直到找到叶子节点就直接返回当前找的深度。

###代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public int minDepth(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        if (root == null) return 0;
        
        int result = 1;
        queue.offer(root);
        while (!queue.isEmpty()) {
            int levelNum = queue.size();
            for (int i = 0; i < levelNum; i++) {
                if (queue.peek().left == null && queue.peek().right == null) return result;
                
                if (queue.peek().left != null) queue.offer(queue.peek().left);
                if (queue.peek().right != null) queue.offer(queue.peek().right);
                queue.poll();
            }
            result++;
        }
        return result;
    }
}

回到目录


###问题:

The count-and-say sequence is the sequence of integers beginning as follows:

1, 11, 21, 1211, 111221, ...

1 is read off as "one 1" or 11. 11 is read off as "two 1s" or 21. 21 is read off as "one 2, then one 1" or 1211.

Given an integer n, generate the nth sequence.

Note: The sequence of integers will be represented as a string.

###大意:

数着说序列是从下面的整型序列开始的:

1, 11, 21, 1211, 111221, ...

1 被称为 "一个1" 或者 11. 11 被称为 "两个1" 或者 21. 21 被称为 "两个2, 然后一个1" 或者 1211.

给出一个整型n,生成第n个序列。

注意:整型序列需要时字符串的形式。

###思路: 乍一看题目没懂意思,后来知道了,就是看上一个序列的数字,念出来是什么样子就写什么样子,比如一个1,两个2一个1,这么念的就这么写。

做法的话其实只能循环着来老老实实做,只是在做的时候要注意一些边界情况,最后输出的是第n个序列,不是整体n个序列的连接,无非就是一个个算下来,判断有几个连续的什么数字,然后加进去,这里利用数组来做循环方便一些,最后再转换成字符串。

有些要注意的是在做序列时由于循环的条件限制,每次序列的最后一段需要额外写一下,而且数组的复制要使用clone的深度复制,否则只是浅复制,拿到一个引用而已,修改数组时两个数组都会发生变化,代码写的比较丑,后面别人写的要稍微好看一点,但是思路还是比较清晰地。

###代码(Java):

public class Solution {
    public String countAndSay(int n) {
        if (n == 0) return "";
        
        int[] resultArray = new int[10000];
        resultArray[0] = 1;
        int index = 1;
        for (int i = 1; i < n; i++) {
            int last = resultArray[0];
            int lastSame = 0;
            int[] tempArray = new int[10000];
            int num = 0;
            
            int j = 0;
            for (; j < index; j++) {
                if (resultArray[j] == last) lastSame++;
                else {
                    tempArray[num] = lastSame;
                    tempArray[num+1] = last;
                    
                    last = resultArray[j];
                    lastSame = 1;
                    num += 2;
                }
            }
            tempArray[num] = lastSame;
            tempArray[num+1] = last;
            num += 2;
            
            index = num;
            resultArray = tempArray.clone();
        }
        
        int[] trueArray = Arrays.copyOfRange(resultArray, 0, index);
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < trueArray.length; i++) {
            sb.append(trueArray[i]);
        }
        return sb.toString();
    }
}

###他山之石:

public class Solution {
    public String countAndSay(int n) {
        
        StringBuilder curr = new StringBuilder();
        StringBuilder prev = new StringBuilder();
        
        if(n<=0) return curr.toString();
        curr.append(1);
        
        for(int i = n; i>1; i--) {
        	
            prev = curr;
            curr = new StringBuilder();
            
            char[] c = prev.toString().toCharArray();
            int count = 1;
            
            for(int j = 1; j <=c.length; j++) {
                
            	if(j == c.length || c[j]!=c[j-1]) {
                    curr.append(count);
                    curr.append(c[j-1]);
                    count = 1 ;
                } else {
                    count++;
                }
                
            }
            
        }
        
        return curr.toString();
    }
    
}

回到目录


###问题:

Given a linked list, remove the nth node from the end of list and return its head.

For example,

Given linked list: 1->2->3->4->5, and n = 2.

After removing the second node from the end, the linked list becomes 1->2->3->5.

Note: Given n will always be valid. Try to do this in one pass.

###大意:

给出一个链表,移除链表的倒数第n个节点并返回链表头。

例子,

给出链表: 1->2->3->4->5, 以及 n = 2. 在移除倒数第二个节点后,列表变为了 1->2->3->5。

注意: 给出的n一定是有效的。 尝试在一轮循环中做。

###思路: 题目的难点在于你不知道遍历到第几个节点时是要删除的倒数第n个节点。

我的做法很笨,遍历一遍记录所有节点的值和位置,然后重新根据值和位置创建新的链表,跳过要删除的那个位置的节点,因为此时知道总节点数了就可以推出是第几个节点了。在操作时要注意一些特殊情况,比如只有一个节点时、删除头结点时要怎么处理。

###代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int[] nodeVal = new int[100];
        int[] nodeIndex = new int[100];
        nodeVal[0] = head.val;
        nodeIndex[0] = 0;
        int index = 1;
        while (head.next != null) {
            head = head.next;
            nodeVal[index] = head.val;
            nodeIndex[index] = index;
            index++;
        }
        
        ListNode newHead;
        int begin = 0;
        if (index == 1) return null;
        else if (index == n) {
            newHead = new ListNode(nodeVal[1]);
            begin = 1;
        } else newHead = new ListNode(nodeVal[0]);
        ListNode tempNode = newHead;
        for (int i = begin+1; i < index; i++) {
            if (i != index - n) {
                ListNode newNode = new ListNode(nodeVal[i]);
                tempNode.next = newNode;
                tempNode = newNode;
            }
        }
        
        return newHead;
    }
}

###他山之石:

public ListNode removeNthFromEnd(ListNode head, int n) {
    
    ListNode start = new ListNode(0);
    ListNode slow = start, fast = start;
    slow.next = head;
    
    //Move fast in front so that the gap between slow and fast becomes n
    for(int i=1; i<=n+1; i++)   {
        fast = fast.next;
    }
    //Move fast to the end, maintaining the gap
    while(fast != null) {
        slow = slow.next;
        fast = fast.next;
    }
    //Skip the desired node
    slow.next = slow.next.next;
    return start.next;
}

看一下这个巧妙的做法,他设了快慢两个标记,初始都在头结点,快的先往后遍历,遍历到与头结点相差为n的时候停止,然后快的和慢的一起往后走,直到快的走到了链表尾节点打止,这时候快慢两个节点间相差的节点数正好是n,也就是说慢的所在的下一个节点正好是要删除的节点,直接跳过去就可以了,一遍遍历完成,很棒。

回到目录


###问题:

Given a pattern and a string str, find if str follows the same pattern.

Here follow means a full match, such that there is a bijection between a letter in pattern and a non-empty word in str.

Examples:

  1. pattern = "abba", str = "dog cat cat dog" should return true.
  1. pattern = "abba", str = "dog cat cat fish" should return false.
  2. pattern = "aaaa", str = "dog cat cat dog" should return false.
  3. pattern = "abba", str = "dog dog dog dog" should return false.

Notes: You may assume pattern contains only lowercase letters, and str contains lowercase letters separated by a single space.

###大意:

给出一个模型和一个字符串,判断字符串是否遵循模型

这里的遵循是指全匹配,模型中的每个字母都映射字符串中的非空单词。

例子:

  1. pattern = "abba", str = "dog cat cat dog" 应该返回 true。
  1. pattern = "abba", str = "dog cat cat fish" 应该返回 false.
  2. pattern = "aaaa", str = "dog cat cat dog" 应该返回 false.
  3. pattern = "abba", str = "dog dog dog dog" 应该返回 false.

注意: 你可以假设模型只包含小写字母,并且字符串包含由空格分开的小写字母单词。

###思路: 题目的意思是模型中的每个字母对应一个单词,相同字母位置对应的单词也要一样。

问题在于怎么判断单词是在之前哪个位置出现过的。

这里的模型和字符串都是字符串,我们先全部转化为字母数组和字符串数组,方便进行比较。

为了记录不同的字母是否出现过以及在哪个位置出现过,同时注意题目提示的模型全为小写字母,我们可以创建两个长度26的数组,当对应字母出现过,就将一个数组的对应位置加一,同时在另一个数组中记录其在模型中出现的位置,也就是模型数组序号,在遍历模型数组时,如果发现记录字母出现次数的数组对应的数量大于0,说明出现过,就可以在记录位置的数组中根据字母找到首次出现的位置了,这里我们其实只需要知道首次出现的位置,如果没出现过,就记录下来。

当发现出现过之后,就要根据记录的首次出现的位置和当前的位置,比较对应两个位置的字符串是否相等,不等则返回false。

如果是第一次在模型中出现的字母,不仅仅要记录下出现的位置,还有一个陷阱在于,这个位置对应的单词应该也是第一次出现,而不应该在之前出现过,否则就不匹配模型的第一次出现这个概念了。判断方法只能是遍历当前位置之前的单词,看有没有相同的单词,有就返回false。

在比较单词是否相同,也就是字符串是否相同时,注意要使用 a.equals(b) 来进行比较,而不能简单地用 == 来比较, == 比较的是两个字符串对象的内存为止,就算内容一样,也会返回不相同。

###代码(Java):

public class Solution {
    public boolean wordPattern(String pattern, String str) {
        String[] strArr = str.split(" ");
        char[] patternArr = pattern.toCharArray();
        if (strArr.length != patternArr.length) return false;
        int[] letter = new int[26];
        int[] index = new int[26];
        for (int i = 0; i < patternArr.length; i++) {
            if (letter[patternArr[i] - 'a'] > 0) {// 出现过
                int nowIndex = index[patternArr[i] - 'a'];
                if (!strArr[i].equals(strArr[nowIndex])) return false;
            } else {// 第一次出现
                if (i != 0) {
                    for (int j = 0; j < i; j++) {
                        if (strArr[i].equals(strArr[j])) return false;
                    }
                }
                letter[patternArr[i] - 'a'] ++;
                index[patternArr[i] - 'a'] = i;
            }
        }
        return true;
    }
}

回到目录


###问题:

Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most one cookie. Each child i has a greed factor gi, which is the minimum size of a cookie that the child will be content with; and each cookie j has a size sj. If sj >= gi, we can assign the cookie j to the child i, and the child i will be content. Your goal is to maximize the number of your content children and output the maximum number.

Note: You may assume the greed factor is always positive. You cannot assign more than one cookie to one child.

Example 1:

Input: [1,2,3], [1,1]

Output: 1

Explanation: You have 3 children and 2 cookies. The greed factors of 3 children are 1, 2, 3. And even though you have 2 cookies, since their size is both 1, you could only make the child whose greed factor is 1 content. You need to output 1.

Example 2:

Input: [1,2], [1,2,3]

Output: 2

Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. You have 3 cookies and their sizes are big enough to gratify all of the children, You need to output 2.

###大意:

假设你是一个很棒的父母,并且想要拍给你的孩子一些饼干。但是你应该给每个小孩至少一个饼干。每个小孩 i 有一个贪婪因子 gi,这是那个小孩能满足的饼干的最小尺寸;而每个饼干 j 都有一个尺寸 sj,如果 sj >= gi,我们就可以把 j 饼干给小孩 i,并且小孩 i 会得到满足。你的目标是给尽量多的小孩饼干并且返回最大数量。

注意: 你可以假设贪婪因子始终是正数。 你不能给超过一块饼干给一个小孩。

例1:

输入: [1,2,3], [1,1] 输出:1 解释:你有三个小孩和两块饼干,三个小孩的贪婪因子为1、2、3。虽然你有两块饼干,但是因为它们的尺寸都是1,所以你只能让贪婪因子是1的小孩满足。所以你需要输出1。

例2:

输入: [1,2], [1,2,3] 输出:2 解释:你有两个小孩和三块饼干,两个小孩的贪婪因子是1、2。你有三块饼干并且他们足够大来满足所有的小孩,你需要输出2。

###思路: 这乍看之下有点复杂,涉及到一个类似最优分配的问题,但其实是简化了的,因为每个小孩最多只能给一块饼干,只用考虑尽量给的小孩多就可以了。

简单地说就是看每个小孩有没有剩下的饼干能满足他,有就给他,没有就过,但是为了给的尽量多,所以大饼干尽量给更贪婪的小孩子,给每个小孩的饼干尽量维持刚好满足他就行了,不过如果实在只能找到大饼干给他那也行,反正最后只要求小孩数量,给谁都是给。

我们先将饼干尺寸和小孩需求都排个序,然后从小到大去遍历地给。在遍历过程中可以取个巧,两个排序后的数组都设一个标记,一起往后移,饼干大小不满足就移饼干的标记,看看后面的饼干能不能满足他,只有满足了才移小孩的标记,因为如果这个小孩都不能满足,后面更贪婪的小孩更加满足不了。循环的结束条件就是小孩或者饼干的标记二者有一个到了数组的尾部,就算停止。

###代码(Java):

public class Solution {
    public int findContentChildren(int[] g, int[] s) {
        if (g.length == 0 || s.length == 0) return 0;
        
        Arrays.sort(g);
        Arrays.sort(s);
        
        int i = 0;
        int j = 0;
        int result = 0;
        while (!(i >= g.length || j >= s.length)) {
            if (s[j] >= g[i]) {
                result ++;
                j++;
                i++;
            } else {
                j++;
            }
        }
        return result;
    }
}

回到目录


###问题:

Given a non-empty string check if it can be constructed by taking a substring of it and appending multiple copies of the substring together. You may assume the given string consists of lowercase English letters only and its length will not exceed 10000.

Example 1:

Input: "abab" Output: True Explanation: It's the substring "ab" twice.

Example 2:

Input: "aba" Output: False

Example 3:

Input: "abcabcabcabc" Output: True Explanation: It's the substring "abc" four times. (And the substring "abcabc" twice.)

###大意:

给出一个非空字符串,检查它是否可以由它的子字符串重复组成。你可以假设给出的字符串全部由小写字母组成并且它的长度不超过10000。

例1:

输入:“abab” 输出:True 解释:重复两次子字符串 “ab”。

例2:

输入:“aba” 输出:False

例3:

输入:“abcabcabc” 输出:True 解释:多次重复子字符串 “abc”。(或者重复两次子字符串 “abcabc”。)

###思路: 这道题我的想法是,检测是否由子字符串重复组成,只需要看是不是可以后面部分的字符串与前面的字符串完全一样就可以了。

首先如果字符串的字母数小于两个,那也不用判断了,一定不可能是多个子字符串重复出来的。

设立两个标记,一个快一点一个慢一点。快的从第二个字母开始往后遍历,找到与第一个字母一样的,就停下来开始判断,慢的从第一个字母开始,两个标记一起往后遍历,看是不是可以完全一致,一直到最后一个字母,如果从后面开始的与从头开始的一模一样,说明是存在重复的字符串,并且由它重复组成的。

如果遍历时又出现不一样了,这时候说明还不是重复的,就要继续当做从头开始找了,继续往后遍历快的标记,找与第一个字母一样的,然后重复上面的过程,注意找到后慢的标记需要回到第一个字母。

不管找没找到,当快的遍历到最后字母了就停止遍历了,这时如果是没找到的状态,那就直接false了,如果是找到的状态,那么有可能是确实重复组成的,也有可能只是最后一小节和前面的一样,中间的一段还是没有重复的,这时候根据慢的标记来判断,如果慢的标记已经走过了整个字符串的一半,就说明至少是二分之一的子字符串重复两次得到的,或者更小的字符串重复多次。如果慢的标记还没走过一半,说明中间还有一部分并没有来得及重复,这时候就依然是false了。在判断慢的是否过半了时,由于存在整个字符串长度可能为基数也可能为偶数的情况,所以用慢标记的位置乘以二来和整体长度作比较进行判断比较合适。

这个方法只需要遍历一次字符串,时间复杂度只有O(n),还是很快的,实际结果也打败了95%的人~

###代码(Java):

public class Solution {
    public boolean repeatedSubstringPattern(String str) {
        if (str.length() < 2) return false;
        
        char[] arr = str.toCharArray();
        int low = 0;
        int fast = 1;
        boolean match = false;// 匹配到与否的标记
        while (fast < arr.length) {
            if (arr[fast] == arr[0]) {
                // 匹配到了第一个,开始判断是否重复
                for (low = 0; fast < arr.length;) {
                    if (arr[low] == arr[fast]) {// 往后走进行不断判断
                        match = true;
                        low++;
                        fast++;
                    } else {// 匹配不一致,跳出去重新找
                        match = false;
                        break;
                    }
                }
            } else {// 没能匹配首字母
                match = false;
                fast++;
            }
        }
        return match && low*2 >= arr.length;
    }
}

回到目录


###问题:

Find the total area covered by two rectilinear rectangles in a 2D plane.

Each rectangle is defined by its bottom left corner and top right corner as shown in the figure.

Assume that the total area is never beyond the maximum possible value of int.

###大意:

计算两个2D矩形覆盖的全部区域面积。

每个矩形都由图中这种左下角和右上角的坐标来定义。

假设整体区域不会大于整型的最大值。

###思路: 一开始弄错了,以为是计算重复面积,后来才发现是计算全部面积,其实也差不多,就是用两个矩形的面积和减去重复覆盖的面积。

计算面积其实很容易,关键在于判断各种可能的情况,判断有没有重复覆盖的面积,以及如何去准确的计算,题目给的用例实在是任性,说好的左下角和右上角,G,H的值却可能比E,F要小= =

做过这道题后不得不说坐标的计算真的要细心并且考虑周全啊,好多粗心犯的错误。

###代码(Java):

public class Solution {
    public int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) {
        if (A > C) {
            int temp = A;
            A = C;
            C = temp;
        }
        if (B > D) {
            int temp = B;
            B = D;
            D = temp;
        }
        if (E > G) {
            int temp = E;
            E = G;
            G = temp;
        }
        if (F > H) {
            int temp = F;
            F = H;
            H = temp;
        }
        
        int width1 = Math.abs(C - A);
        int height1 = Math.abs(D - B);
        int width2 = Math.abs(G - E);
        int height2 = Math.abs(H - F);
        
        // 两个长方形面积
        int area1 = width1 * height1;
        int area2 = width2 * height2;
        
        // 计算重合部分
        // 重合部分为0
        if (A + width1 <= E || E + width2 <= A || B + height1 <= F || F + height2 <= B) return area1 + area2;
        // 重合部分不为0
        int width;
        if (E == A) width = Math.min(width1, width2);
        else if (E > A) width = Math.abs(E - A) + width2 < width1 ? width2 : width1 - Math.abs(E - A);
        else width = Math.abs(A - E) + width1 < width2 ? width1 : width2 - Math.abs(A - E);
        
        int height;
        if (F == B) height = Math.min(height1, height2);
        else if (F > B) height = Math.abs(F - B) + height2 < height1 ? height2 : height1 - Math.abs(F - B);
        else height = Math.abs(B - F) + height1 < height2 ? height1 : height2 - Math.abs(B - F);
        
        return area1 + area2 - width * height;
    }
}

###他山之石:

public class Solution {
    public int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) {
        int left = Math.max(A,E), right = Math.max(Math.min(C,G), left);
        int bottom = Math.max(B,F), top = Math.max(Math.min(D,H), bottom);
        return (C-A)*(D-B) - (right-left)*(top-bottom) + (G-E)*(H-F);
    }
}

这个计算重复面积的方式很巧妙,简单多了。

回到目录


###问题:

Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "([)]" are not.

###大意:

给出一个只包含 '(', ')', '{', '}', '[' 和 ']' 的字符串,判断它的输入是否是有效的。

括号必须是以正确的顺序关闭的, "()" 和 "()[]{}" 都是有效的,但是 "(]" 和 "([)]" 是无效的。

###思路: 题目的要求说来也简单,就是判断括号是不是有效的,自己先用测试用例试了一下,括号中包含括号也是有效的。

其实无效的情况也就几种,左括号匹配到了不一样的右括号、左括号多了、右括号多了,我用一个数组记录不同位置出现的括号的种类,出现新的右括号的时候判断是否匹配到了正确的括号,还要看是不是是多了的右括号,最后看有没有多出来的左括号。方法很笨,代码也写的很冗余,不过速度还可以。

###代码(Java):

public class Solution {
    public boolean isValid(String s) {
        int[] markArr = new int[s.length()];// 1表示(),2表示[],3表示{}
        
        char[] arr = s.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == '(') {
                markArr[i] = 1;
            } else if (arr[i] == ')') {
                boolean hasMatched = false;
                for (int j = i-1; j >= 0; j--) {
                    if (markArr[j] > 0 && markArr[j] != 1) return false;
                    else if (markArr[j] == 1) {
                        markArr[j] = 0;
                        hasMatched = true;
                        break;
                    }
                }
                if (!hasMatched) return false;
            } else if (arr[i] == '[') {
                markArr[i] = 2;
            } else if (arr[i] == ']') {
                boolean hasMatched = false;
                for (int j = i-1; j >= 0; j--) {
                    if (markArr[j] > 0 && markArr[j] != 2) return false;
                    else if (markArr[j] == 2) {
                        markArr[j] = 0;
                        hasMatched = true;
                        break;
                    }
                }
                if (!hasMatched) return false;
            } else if (arr[i] == '{') {
                markArr[i] = 3;
            } else if (arr[i] == '}') {
                boolean hasMatched = false;
                for (int j = i-1; j >= 0; j--) {
                    if (markArr[j] > 0 && markArr[j] != 3) return false;
                    else if (markArr[j] == 3) {
                        markArr[j] = 0;
                        hasMatched = true;
                        break;
                    }
                }
                if (!hasMatched) return false;
            }
        }
        
        for (int i = 0; i < markArr.length; i++) {
            if (markArr[i] > 0) return false;
        }
        return true;
    }
}

###他山之石:

public class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<Character>();
        // Iterate through string until empty
        for(int i = 0; i<s.length(); i++) {
            // Push any open parentheses onto stack
            if(s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) == '{')
                stack.push(s.charAt(i));
            // Check stack for corresponding closing parentheses, false if not valid
            else if(s.charAt(i) == ')' && !stack.empty() && stack.peek() == '(')
                stack.pop();
            else if(s.charAt(i) == ']' && !stack.empty() && stack.peek() == '[')
                stack.pop();
            else if(s.charAt(i) == '}' && !stack.empty() && stack.peek() == '{')
                stack.pop();
            else
                return false;
        }
        // return true if no open parentheses left in stack
        return stack.empty();
    }
}

这个做法用到了Stack栈这个类型,确实这道题很适合用栈来做,先进后出,遇到左括号的时候放进去,遇到右括号的时候从栈顶拿括号进行匹配,匹配失败就错了,全部匹配正确而且最后栈里没东西了就对了。代码简洁多了。

回到目录


###问题:

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 j is at most k.

###大意:

给出一个整型数组和一个整型数k,判断数组中是否任何两个不一样的位置i和j,如果 nums[i] = nums[j] ,i和j的距离不大于k。

###思路: 这道题看起来简单,不过有很多陷阱,比如如果k = 0,那么无论数组如何都是错的。如果数组中不存在一样的两个数,也是错的。如果数组中存在多个一样的同一个数,只要有最短的两个的距离小于等于k就可以了等等,我把代码缝缝补补后,还是在一个很长数组的测试用例下超时了。。。不过我用的是最直接的方法,看了看如果合理地使用一些数据结构,就会很方便,比如使用set,set集合的特性是里面不会出现两个不一样的数字,那么我们建立一个长度为k的set,用它来扫描整个数组,不断地判断新出现的数据能不能放进去,如果不能放进去,说明存在距离小于等于k的数据是有相等的,否则就可以放进去,当然set中的数据量如果超过k了就要同时把早先放进去的数据拿出来了,如果扫描过后都可以放进去和取出来,说明没找到小于等于k的相等的数,那就错了。

这道题有个地方不一样在于,一般的题目都是碰到什么就返回false,都没有false才返回true。而这道题却是遇到什么则返回true,都没有true,才返回false。

###他山之石:

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]);
            if(!set.add(nums[i])) return true;
        }
        return false;
 }

回到目录


###问题:

Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array.

Note:

You may assume that nums1 has enough space (size that is greater or equal to m + n) to hold additional elements from nums2. The number of elements initialized in nums1 and nums2 are m and n respectively.

###大意:

给出两个排序好了的整型数组 nums1 和 nums2,将 nums2 合并到 nums1 中去成为一个排好序的数组。

注意:

你可以假设 nums1 有足够的空间(尺寸大于等于 m + n)来添加从 nums2 来的额外的元素。nums1 和 nums2 中的元素个数分别为 m 和 n。

###思路: 这道题一开始并不想用最直接最笨的方法做,而是试图利用题目的条件来做。题目两个原本的数组都是排好序了的,题目没有返回值,因此只能在nums1中直接操作,不能放新的数组,题目额外给出了元素个数m和n,这都是暗示。不过最终也没做好,后来发现其实还是思路方向反了= =

这里先说最直接的办法,空间换时间,直接创建一个新数组,然后一个个从两个数组中按照顺序拿元素去比较大小放回nums1中,就得到一个排好序的数组了。

###代码(Java):

public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        if (nums2.length == 0) return;
        
        int[] nums = Arrays.copyOfRange(nums1, 0, m);
        int i = 0;
        int j = 0;
        int k = 0;
        while (i < nums.length && j < nums2.length) {
            if (nums[i] <= nums2[j]) {
                nums1[k] = nums[i];
                i++;
            } else {
                nums1[k] = nums2[j];
                j++;
            }
            k++;
        }
        while (i < nums.length) {
            nums1[k] = nums[i];
            i++;
            k++;
        }
        while (j < nums2.length) {
            nums1[k] = nums2[j];
            j++;
            k++;
        }
        
        return;
    }
}

###他山之石:

public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i=m-1, j=n-1, k=m+n-1;
        while (i>-1 && j>-1) nums1[k--]= (nums1[i]>nums2[j]) ? nums1[i--] : nums2[j--];
        while (j>-1)         nums1[k--]=nums2[j--];
        
        return;
    }
}

这个做法让我明白我的思路还是反了,之前一直想的还是顺序去找元素,还在想怎么处理原来位置的元素,这个方法直接从后面开始排,先放最后面的数,从大到小慢慢往前走,放的数一定不会覆盖原来nums1中的数,因为有足够的位置。如果nums2中的数先放完,那么剩下的nums1前面的数位置不用动,如果nums1的先放完,剩下还有一些nums2的数就直接放在前面去了。

此外,这里为了精简代码,使用了i--这种先取值然后减一的特性。

回到目录


###问题:

Given a singly linked list, determine if it is a palindrome.

Follow up:

Could you do it in O(n) time and O(1) space?

###大意:

给出一个单链表,判断它是否是回文。

进阶:

你能在O(n)的时间复杂度和O(1)的空间复杂度下来做吗?

###思路: 回文的意思就是正着读反着读都是一样的。

这道题我使用简单的思路,一个个遍历链表节点来倒序组成一个新链表,然后和旧链表一起遍历看节点是不是一样的,如果一样说明是回文,否则不是。这个方法很简单,时间复杂度是O(n),但是空间复杂度也是O(n),并不符合进阶的要求。

###代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) return true;
        
        ListNode newHead = head;
        ListNode lastNode = new ListNode(head.val);
        while (newHead.next != null) {
            newHead = newHead.next;
            ListNode newNode = new ListNode(newHead.val);
            newNode.next = lastNode;
            lastNode = newNode;
        }
        
        if (head.val != lastNode.val) return false;
        while (head.next != null) {
            head = head.next;
            lastNode = lastNode.next;
            if (head.val != lastNode.val) return false;
        }
        
        return true;
    }
}

###他山之石:

public class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast = head, slow = head;
        Stack<ListNode> stack = new Stack<>();
        while(fast != null && fast.next != null){
            stack.push(slow);
            fast = fast.next.next;
            slow = slow.next;
        }
// for odd list
        if(fast != null){
            slow = slow.next;
        }
        while(slow != null){
            if(stack.pop().val != slow.val) return false;
            slow = slow.next;
        }
        return true;
    }
}

这个做法跟我的不一样,使用了快慢两个标记,快的那个只有一个用处,就是以两倍速度遍历,引导慢的标记到达链表最中心,当然这里要根据链表个数是奇数还是偶数来分开判断,也是看fast是正好跑到最后面还是跑过了来判断。找到中心后,利用栈存放的数据先进后出的特性,从中间往两头一起遍历,看遍历的值是不是都一样,一样则是回文,否则不是。这个做法同样的时间复杂度是O(n),二空间复杂度是O(n),因为用到了一个栈。不过速度应该比我的要快一半

回到目录


问题:

Implement the following operations of a stack using queues.

  • push(x) -- Push element x onto stack.
  • pop() -- Removes the element on top of the stack.
  • top() -- Get the top element.
  • empty() -- Return whether the stack is empty.

Notes:

  • You must use only standard operations of a queue -- which means only push to back, peek/pop from front, size, and is empty operations are valid.
  • Depending on your language, queue may not be supported natively. You may simulate a queue by using a list or deque (double-ended queue), as long as you use only standard operations of a queue.
  • You may assume that all operations are valid (for example, no pop or top operations will be called on an empty stack).

大意:

使用队列实现下面的栈操作:

  • push(x)--将元素xpush到栈中去。
  • pop()--移除栈顶的元素。
  • top()--获取栈顶的元素。
  • empty()--返回栈是否为空。

注意:

  • 你只能使用队列的标准操作——也就是只有push到队尾、从队首peek/pop、size和是否为empty操作是有效的。
  • 根据你的语言,队列可能不是原生支持的。你可以使用list或者deque(双尾队列)模仿一个队列,只要你只使用队列的标准操作。
  • 你可以假设所有的操作都是有效的(比如,不会pop或者top一个空栈)。

思路:

这道题和232. Implement Queue using Stacks很像,跟他是相反的要求。

其实用队列实现栈无非就是一个出的顺序不一样,栈是后进先出,队列是先进先出,因此要么改变队列出的做法,全部出完直到最后一个才是作为栈需要出的;要么改入队的做法,每次入的时候都全部取出来一遍,将新元素入在队首去,这样出的时候就是第一个出的了。

因为题目的代码要求有两个取元素操作,一个添加元素操作,所以我采用了第二种做法,改变入队方式,具体实现还是很简单的。

代码(Java):

class MyStack {
    Queue<Integer> q;
    
    public MyStack(){
        q = new LinkedList<Integer>();
        //temp = new LinkedList<Integer>();
    }
    
    // Push element x onto stack.
    public void push(int x) {
        Queue<Integer> temp = new LinkedList<Integer>();
        while (q.peek() != null) {
            temp.add(q.poll());
        }
        q.add(x);
        while (temp.peek() != null) {
            q.add(temp.poll());
        }
    }

    // Removes the element on top of the stack.
    public void pop() {
        q.remove();
    }

    // Get the top element.
    public int top() {
        return q.peek();
    }

    // Return whether the stack is empty.
    public boolean empty() {
        return q.peek() == null;
    }
}

回到目录


问题:

Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the length of last word in the string.

If the last word does not exist, return 0.

Note: A word is defined as a character sequence consists of non-space characters only.

For example, Given s = "Hello World", return 5.

##大意:

给出一个由大小写字母和空格组成的字符串s,返回其最后一个单词的长度。

如果最后一个单词不存在,返回0。

注意:单词是指仅有非空格字符组成的序列。

例子: 给出 s = "Hello World", 返回 5。

##思路: 题目并不难,只是要注意,我在用测试用例尝试的时候发现,如果给出的字符串最后是空格,也会取空格前最后一个单词的长度,也就是说即使最后是多个空格,只要前面还有单词,就将其视为最后一个单词。

我的做法是遍历数组,遇到字符串第一个就是字母或者说字母前一个字符是空格时,就将其视为一个单词的开始,令结果回到1,重新计算长度,其余遇到字母就将结果加一,遇到空格就不管了。

##代码(Java):

public class Solution {
    public int lengthOfLastWord(String s) {
        int length = 0;
        char[] arr = s.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            if (i == 0) {
                if (arr[i] != ' ') length ++;
            } 
            else if (arr[i-1] == ' ' && arr[i] != ' ') length = 1;
            else if (arr[i] != ' ') length ++;
        }
        return length;
    }
}

##他山之石:

public class Solution {
	public int lengthOfLastWord(String s) {
		int len = s.length();
		int i = len -1;
		int empty = 0;
		if(s == null || len == 0)
			return 0;
		while(i>=0 && s.charAt(i)==' '){
			i--;
			empty++;
		}
		while(i>=0 && s.charAt(i)!=' ')
			i--;
		return len- empty - (i+1);
	}
}

这个做法是从后往前遍历字符串,末尾如果有空格,先把空格去除干净,然后取字母的长度,再次遇到空格打止,因为是从后往前遍历,不需要全部遍历完整个字符串,所以会快一点。

回到目录


问题:

Remove all elements from a linked list of integers that have value val.

Example Given: 1 --> 2 --> 6 --> 3 --> 4 --> 5 --> 6, val = 6 Return: 1 --> 2 --> 3 --> 4 --> 5

大意:

移除链表中素有值为val的元素。

例子: 给出: 1 --> 2 --> 6 --> 3 --> 4 --> 5 --> 6, val = 6 返回: 1 --> 2 --> 3 --> 4 --> 5

思路:

这道题没什么特殊的,遍历链表遇到值跟val相同的就删除节点就好,链表删除节点的做法也比较固定了,只是要细心一点不要漏了什么特殊情况,比如删除节点时是最后一个节点的处理、链表头部连续出现要删除的节点时的处理等等,另外因为最终要返回的是头结点,所以这个节点要有一个变量去保留着最后返回。

我的做法是先把头部的连续的目标节点都删掉,然后遍历链表,中间遇到了就删,当然遇到时要判断是不是最后一个节点了,是的话就next直接指向null了。在删头结点时,因为可能循环着遇到后面没节点了的情况,比如 1 --> 1 这种全部要删除的,所以在循环条件中利用 && 运算符的特性,先判断节点存在,然后再判断值,如果节点不存在,第一个判断条件就无效,那么就不会处理第二个判断条件,否则直接判断值有可能在无节点时报错。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) return head;
        
        ListNode result = head;
        while (result != null && result.val == val) result = result.next;
        
        while (head.next != null) {
            if (head.next.val == val) {
                if (head.next.next != null) head.next = head.next.next;
                else head.next = null;
            } else {
                head = head.next;
            }
        }
        
        return result;
    }
}

回到目录


问题:

Find the nth digit of the infinite integer sequence 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...

Note: n is positive and will fit within the range of a 32-bit signed integer (n < 231).

Example 1:

Input: 3

Output: 3

Example 2:

Input: 11

Output: 0

Explanation: The 11th digit of the sequence 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... is a 0, which is part of the number 10.

大意:

在无限整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 中找到第n个数字。

注意: n是个正数而且会在32位范围内(n<2的31次方)

例1:

输入:3 输出:3

例2:

输入:11 输出:0 解释:序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 的第11个数是数字10中的0。

思路:

开始没看到意思,后来明白了,当序列中的数字是两位数、三位数等等后,第n个数就不再是序列中的第n个数了,比如10中的1是第10个数字,0是第11个数字。

这么一来,要找到第n个数,首先要知道这个数所在的序列中的数字是什么,我们只能先判断当前的是几位数,因为每多一位数,其范围内的数字个数会变成上一轮的10倍,比如个位数有9个,两位数有90个,三位数有900个。。。两位数对应的数字有902个,三位数有9003个,所以可以通过这个规律先判断要找的序列数字是几位数。

找出是几位数后,在通过对位数的除法得出数字是多少,比如如果n是12,那么先通过其大于9,小于90*2+9,得知是两位数,然后通过(12-9)/2 = 1得出其至少是9+1,也就是10或者10以后的数字,具体是10还是11,要看有没有余数,这里(12-9)%2=1,所以余数为1,也就是超过了10,是10的后一个数字的第一个数。其实这里很简单我们直接就可以知道是11中的第一个1。

如果余数是0,说明就是当前找到的数字的最后一位,直接将该数字对10取余即可得到需要的个位数字。如果余数大于0,说明是下一个序列数字中的数,然后根据余数来判断是下个序列数字中的第几个数。要得到一个多位数中的某位数,有多种做法,一种是化为字符数组直接取,另一种我用的是先取余再做除法,比如要得到1245这个数字中的第三个数字4,先让其对100取余数得到45,然后对10做触发得到4。至于怎么知道是100和10,就可以通过是第几位来进行10的次方运算了,这个直接看到代码就可以明白,只是有点长,要想清楚。

还有一点要注意的是,提交时我在一个很大的数上出了错,因为题目给的n的范围是小于2的31次方,这个数字在上述处理过程中可能会有大到超过int型变量范围的数字,因此不得不全部使用了long型变量表示数字。另外Math.pow()这个次方计算操作的两个参数必须是double型的,因此也不得不进行了强制转换又转换。

代码(Java):

public class Solution {
    public int findNthDigit(int n) {
        if (n <= 9) return n;
        
        long lastNum = 9;
        long reduce = 9;
        long num = 90;
        long i = 2;
        while (n > reduce + num * i) {
            lastNum = lastNum + num;
            reduce = reduce + num * i;
            num = num * 10;
            i++;
        }
        long over = (long)n - reduce;
        System.out.println(lastNum + over / i);
        long index = over / i;
        long reste = over % i;
        if (reste == 0) return (int)((lastNum + index) % 10);
        else return (int)(((lastNum + index + 1) % (long)Math.pow(10.0, (double)(i - reste + 1))) / (long)Math.pow(10.0, (double)(i - reste)));
    }
}

回到目录


问题

Write a function to find the longest common prefix string amongst an array of strings.

大意:

写一个函数来寻找一个数组的字符串中最长的相同前缀。

思路:

题目描述很简单只有一句话,导致我理解错了,以为是找到存在的任意两个字符串最长的相同前缀,还写了一份代码出来,自己测试的时候发现题目想要的结果不符合才发现,其实是找整个数组的字符串都有的相同的最长前缀,这就方便多了。

我采用从第一个字符串的前头开始一个一个地增加前缀长度来判断后面所有的字符串是否有同样的前缀,然后返回结果。

在这个过程中有很多特殊情况要注意,比如如果数组长度为0,直接返回空字符串;如果只有一个字符串,直接返回该字符串;如果存在某个字符串(包括第一个)就是“”这样的空字符串,直接返回“”;每次增加前缀长度的时候要判断第一个字符串是否够长,不够了就直接返回当前结果;只有当所有后面的字符串都存在前缀时才可以认为是相同的,只要有一个不相同就可以直接返回之前已经记录的前缀了等等。

代码(Java):

public class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs.length == 0) return "";
        else if (strs.length == 1) return strs[0];
        
        int end = 1;
        String result = "";
        String firstStr = strs[0];
        if (firstStr.length() == 0) return "";
        else {
            String sub;
            while (end <= firstStr.length()) {
                sub = firstStr.substring(0, end);
                for (int i = 1; i < strs.length; i++) {
                    String nowStr = strs[i];
                    if (nowStr.length() == 0) return "";
                    else {
                        if (nowStr.startsWith(sub, 0)) {
                            if (i == strs.length-1) {
                                result = sub;
                                end ++;
                            }
                        } else return result;
                    }
                }
            }
        }
        
        return result;
    }
}

他山之石:

public class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs.length == 0) return "";
        String pre = strs[0];
        for (int i = 1; i < strs.length; i++)
            while(strs[i].indexOf(pre) != 0) {
                pre = pre.substring(0,pre.length()-1);
                if (pre.length() == 0) return "";
            }
        return pre;
    }
}

这个做法是先用整个第一个字符串来当做前缀去判断每个字符串是否拥有该前缀,没有就将这个判断的前缀末尾字符去掉再比较,直到当前判断的字符串有这个前缀了,才去判断下一个字符串有没有,执行同样的操作。当任何时候前缀长度减少到0了,也可以直接返回了。这个做法会快一点,而且免去了很多特殊情况的判断,代码很简洁。

回到目录


问题:

Given two binary strings, return their sum (also a binary string).

For example, a = "11" b = "1" Return "100".

大意:

给出两个二进制字符串,返回它们的和(同样是二进制字符串)。

例子: a = "11" b = "1" 返回 "100".

思路:

这其实就是一个模拟二进制加法的问题。如果某一位两个数都是0,结果的这一位也为0,有一个1就是1,如果都是1,这一位是0,还有进一位,所以上面三种情况还要考虑低位有没有进位。

因为两个二进制数的长度可能不同,所以我们先要在一个循环中去加两个数的和,当任一个数处理完之后,再看剩下哪个,将其加到结果中去。

这种做法速度还是很快的,只是代码比较多,也可以直接将短的那个加到长的那个上面去,但最后还是要根据进位遍历一遍长的剩余部分,节省不了太多东西。

代码(Java):

public class Solution {
    public String addBinary(String a, String b) {
        int flag = 0;
        char[] result = new char[a.length() > b.length() ? a.length() : b.length()]; 
        int k = result.length - 1;
        int i = a.length() - 1;
        int j = b.length() - 1;
        for (; i >= 0 && j >= 0; i--, j--, k--) {
            if (a.charAt(i) == '1' && b.charAt(j) == '1') {
                result[k] = flag == 1 ? '1' : '0';
                flag = 1;
            } else if (a.charAt(i) == '1' || b.charAt(j) == '1') {
                result[k] = flag == 1 ? '0' : '1';
                flag = flag == 1 ? 1 : 0;
            } else {
                result[k] = flag == 1 ? '1' : '0';
                flag = 0;
            }
        }
        if (i >= 0) {
            for (; i >=0; i--, k--) {
                if (a.charAt(i) == '1' && flag == 1) {
                    result[k] = '0';
                    flag = 1;
                } else if (a.charAt(i) == '1' || flag == 1) {
                    result[k] = '1';
                    flag = 0;
                } else {
                    result[k] = '0';
                    flag = 0;
                }
            }
        }
        if (j >= 0) {
            for (; j >=0; j--, k--) {
                if (b.charAt(j) == '1' && flag == 1) {
                    result[k] = '0';
                    flag = 1;
                } else if (b.charAt(j) == '1' || flag == 1) {
                    result[k] = '1';
                    flag = 0;
                } else {
                    result[k] = '0';
                    flag = 0;
                }
            }
        }
        return flag == 1 ? "1" + String.valueOf(result) : String.valueOf(result);
    }
}

回到目录


问题:

Write a program to find the node at which the intersection of two singly linked lists begins.

For example, the following two linked lists:

begin to intersect at node c1.

Notes:

  • If the two linked lists have no intersection at all, return null.
  • The linked lists must retain their original structure after the function returns.
  • You may assume there are no cycles anywhere in the entire linked structure.
  • Your code should preferably run in O(n) time and use only O(1) memory.

大意:

写一个函数来寻找两个单链表开始交汇的节点。

比如,下面这两个链表:

从节点 c1 开始交汇

注意:

  • 如果两个链表完全不交汇,返回 null。
  • 函数返回后原链表必须保持初始结构。
  • 你可以假设整个链表结构都没有任何循环。
  • 你的代码应该在O(n)的时间和O(1)的内存中完成。

思路:

题目的难点在于在O(n)的时间内完成这个事情,那么普通地遍历去比对就不合适了,因为完全不知道链表会在哪个节点开始交汇,而且这个节点在两个链表中的位置也不一定是一样的,所以这里我利用堆栈。

首先遍历两个链表将其中的节点都放入两个栈中,利用栈后进先出的特性来取节点。因为两个链表如果有交汇,后面的节点一定都是一样的,所以两个栈同时取节点进行比对,当出现不同节点时就表示上一个节点时交汇点。

注意“==”这个等号表示比对两个对象的引用是否相等。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        boolean hasFind = false;
        ListNode a = headA;
        ListNode b = headB;
        
        Stack<ListNode> stackA = new Stack<ListNode>();
        Stack<ListNode> stackB = new Stack<ListNode>();
        
        while (a != null) {
            stackA.push(a);
            a = a.next;
        }
        while (b != null) {
            stackB.push(b);
            b = b.next;
        }
        
        if (stackA.empty() || stackB.empty()) return null;
        
        ListNode result = null;
        while (!(stackA.empty() || stackB.empty())) {
            if (stackA.peek() == stackB.peek()) {
                result = stackA.peek();
                stackA.pop();
                stackB.pop();
            } else {
                return result;
            }
        }
        return result;
    }
}

他山之石:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //boundary check
        if(headA == null || headB == null) return null;
        
        ListNode a = headA;
        ListNode b = headB;
        
        //if a & b have different len, then we will stop the loop after second iteration
        while( a != b){
        	//for the end of first iteration, we just reset the pointer to the head of another linkedlist
            a = a == null? headB : a.next;
            b = b == null? headA : b.next;    
        }
        
        return a;
    }
}

这个做法很巧妙的地方在于其循环体的内容,每轮循环都将两个链表的标记往后移动一个,当移动到末尾后就跳到另一个链表头再移动,循环的结束条件是两个标记相同。什么情况下会相同呢?两种情况,一是遇到了相同节点,另一个是完全没有相同节点,由于都会遍历一次两个链表,所以会在同时到达null,如果两个链表长度一直,那么不用跳,直接遍历一次没有交汇就都同时到null了。如果有交汇的,那一定是第一个交汇点。

回到目录


问题:

Given an array of integers A and let n to be its length.

Assume Bk to be an array obtained by rotating the array A k positions clock-wise, we define a "rotation function" F on A as follow:

F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1].

Calculate the maximum value of F(0), F(1), ..., F(n-1).

Note: n is guaranteed to be less than 105.

Example:

A = [4, 3, 2, 6]

F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25 F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16 F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23 F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26

So the maximum value of F(0), F(1), F(2), F(3) is F(3) = 26.

大意:

给出一个整型数组A,设n为其长度。

假设Bk是将A进行k此顺时针旋转后的数组,我们定义一个A的“旋转函数”F,如下:

F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1].

注意: n保证不会超过10的5次方

例子:

A = [4, 3, 2, 6]

F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25 F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16 F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23 F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26

所以 F(0), F(1), F(2), F(3) 中最大的值为 F(3) = 26.

思路:

这个题目的意思就是对A进行旋转求多项式,每次旋转系数移动一次,旋转一次求出一个结果,看哪个最大就返回哪个。

我的做法是直接进行每一个的计算然后找最大的,代码挺简单,时间复杂度是O(n平方),很长。

代码(Java):

public class Solution {
    public int maxRotateFunction(int[] A) {
        if (A.length == 0) return 0;
        int result = -2147483648;
        for (int i = 0; i < A.length; i++) {
            int sum = sum(A, A.length - i);
            if (sum > result) result = sum;
        }
        return result;
    }
    
    public int sum(int[] A, int index) {
        int sum = 0;
        for (int i = 0; i < A.length; i++) {
            if (index >= A.length) index = 0;
            sum += i * A[index];
            index ++;
        }
        return sum;
    }
}

他山之石:

public class Solution {
    public int maxRotateFunction(int[] A) {
		int allSum = 0;
		int len = A.length;
		int F = 0;
		for (int i = 0; i < len; i++) {
		    F += i * A[i];
		    allSum += A[i];
		}
		int max = F;
		for (int i = len - 1; i >= 1; i--) {
		    F = F + allSum - len * A[i];
		    max = Math.max(F, max);
		}
		return max; 
    }
}

这个做法的好处在于只需要O(n)的时间,快很多。他对题目的要求进行了一些数学计算,然后得出了一个方便计算的式子,过程如下:

F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1] F(k-1) = 0 * Bk-1[0] + 1 * Bk-1[1] + ... + (n-1) * Bk-1[n-1] = 0 * Bk[1] + 1 * Bk[2] + ... + (n-2) * Bk[n-1] + (n-1) * Bk[0]

那么,

F(k) - F(k-1) = Bk[1] + Bk[2] + ... + Bk[n-1] + (1-n)Bk[0] = (Bk[0] + ... + Bk[n-1]) - nBk[0] = sum - nBk[0]

因此,

F(k) = F(k-1) + sum - nBk[0]

那Bk[0]是什么呢?

k = 0; B[0] = A[0]; k = 1; B[0] = A[len-1]; k = 2; B[0] = A[len-2]; ...

这样,也就有了上面的代码了。

回到目录


问题:

Reverse bits of a given 32 bits unsigned integer.

For example, given input 43261596 (represented in binary as 00000010100101000001111010011100), return 964176192 (represented in binary as 00111001011110000010100101000000).

Follow up: If this function is called many times, how would you optimize it?

大意:

翻转32位无符号整型数。

比如给出输入为 43261596 (二进制表示为 00000010100101000001111010011100),返回 964176192 (二进制表示为 00111001011110000010100101000000)。

进阶: 如果这个函数被频繁调用,你怎么优化它?

思路:

一开始用笨办法先一位位转化成代表二进制数的数组然后重新计算出结果,但是题目给出的是32位无符号数,也就是说大于int型的范围了,很麻烦。

后来看了看发现根本不需要这么麻烦,直接进行位移运算,因为是要翻转过来,所以一边向右位移输入的数字,一边根据右移后原数字跟1的与操作结果来将结果数字左移,最后直接返回就完了,完全不需要转成二进制又转回来,都是一样的。

不过这里要注意右移时要使用无符号右移。

他山之石:

public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        int result = 0;
        for (int i = 0; i < 32; i++) {
            result += n & 1;
            n >>>= 1;   // 必须进行无符号位移
            if (i < 31) // 最后一次不需要进行位移
                result <<= 1;
        }
        return result;
    }
}

回到目录


问题:

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].

大意:

给出一个整型数组,返回其中两个可以相加得出目的数字的数字的位置。

你可以假设所有输入都有一个解决方案。

例子:

给出 nums = [2, 7, 11, 15], target = 9,

因为 nums[0] + nums[1] = 2 + 7 = 9, 返回 [0, 1]。

思路:

一开始我的做法是用一个长度为目的数字的数组来记录给出的数组中所有小于目的数的数字所在的位置,然后遍历这个数组来看有没有能相加等于目的数的,最后取出其位置。但是当发现给出的数组中可能有负数时,这个做法就崩了。

重新分析一下要求,其实我们有两个需求,一是记录出现过的数字,由于可能有负数,因此已经无法缩小要记录的数字的范围了,二是要记录数字所在的位置,不能做个排序然后位置就不知道了。基于这两个要求,其实HashMap很符合我们的需要。

我们用一个HashMap来记录遍历过程中每次出现的数字及其位置,数值是key,位置是值,同时判断之前有没有记录过正好与当前数字相加等于目的数的数字,有就取出这两个数字的位置,如果遍历完了还没有那就是没有了。

代码(Java):

public class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(target - nums[i])) {
                result[0] = map.get(target - nums[i]);
                result[1] = i;
                return result;
            }
            map.put(nums[i], i);
        }
        return result;
    }
}

回到目录


问题:

Implement strStr().

Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

大意:

实现 strStr()。

返回 haystack 中第一次出现 needle 的位置,如果 needle 不是 haystack 的一部分则返回 -1。

思路:

最简单的做法就是遍历 haystack ,发现和 needle 第一个字符相同的元素,就开始逐个比较,如果全部比完发现一样,那么返回之前那个开始位置就可以了,如果中途有不一样的,那么就要从之前的位置的下一个位置开始继续遍历寻找。

代码(Java):

public class Solution {
    public int strStr(String haystack, String needle) {
        if (needle.equals("")) return 0;
        
        char[] haystackArr = haystack.toCharArray();
        char[] needleArr = needle.toCharArray();
        int i = 0;
        int j = 0;
        for (; i < haystackArr.length;i++) {
            if (haystackArr[i] == needleArr[j]) {
                for (int k = i; k < haystackArr.length && j < needleArr.length; k++, j++) {
                    if (haystackArr[k] != needleArr[j]) break;
                }
                if (j == needleArr.length) return i;
                else j = 0;
            }
        }
        return -1;
    }
}

他山之石:

public class Solution {
    public int strStr(String haystack, String needle) {
        int l1 = haystack.length(), l2 = needle.length();
        if (l1 < l2) {
            return -1;
        } else if (l2 == 0) {
            return 0;
        }
        int threshold = l1 - l2;
        for (int i = 0; i <= threshold; ++i) {
            if (haystack.substring(i,i+l2).equals(needle)) {
                return i;
            }
        }
        return -1;
    }
}

之前的做法耗时很长,因为遇到一样的之后都要进行逐个比较。这个做法就比较简单了,先把集中特殊情况处理了,然后直接比较子字符串是否相等,除去了单个字符比较的麻烦,时间省了很多。

回到目录


问题:

Given a non-empty array of integers, return the third maximum number in this array. If it does not exist, return the maximum number. The time complexity must be in O(n).

Example 1:

Input: [3, 2, 1]

Output: 1

Explanation: The third maximum is 1.

Example 2:

Input: [1, 2]

Output: 2

Explanation: The third maximum does not exist, so the maximum (2) is returned instead.

Example 3:

Input: [2, 2, 3, 1]

Output: 1

Explanation: Note that the third maximum here means the third maximum distinct number. Both numbers with value 2 are both considered as second maximum.

大意:

给出一个非空整型数组,返回数组中第三大的数。如果不存在,就返回最大的数。时间复杂度必须为O(n)。

例1:

输入:[3, 2, 1]

输出:1

解释:第三大的数为1。

例2:

输入:[1, 2]

输出:2

解释:不存在第三大的数,所以要用最大的数 2 来代替。

例3:

输入:[2, 2, 3, 1]

输出:1

解释:注意这里第三大的数是指区分的数字。两个 2 都被视为第二大的数。

思路:

题目要求时间复杂度为O(n),所以排除使用先排序的方法来做,排序后基本时间复杂度就超了。

所以只能遍历一遍来记录第三大的数,我们用三个整型变量来记录,由于题目没说有没有负数,所以我们没法定义一个绝对小于数组中所有数字的初始值,只能以数组中第一个数字来作为初始值,然后遍历一个一个数字去比较看应该替代三个数字中哪个数字,注意如果比第一个数字大,那么原本第一个数字的值要移动到第二个,原本第二个数字的值要移动到第三个,如果替代的是第二个数字,同样要把原本第二个数字的值移动到第三个,道理很简单。由于我们是用第一个数字作为初始值的,因此在替换数字时还有一个原因就是如果第二个和第三个数字还是初始值,而出现了与初始值不同的数字,那就不要求比原数字大了,直接替换并后移,否则如果第一个数字最大,那即使有第三大数字也不会记录下来。

由于题目说了是非空数组,所以不用考虑空数组特殊情况。

最后要判断三个数字是不是不一样,不一样才返回第三大数字,否则就要返回最大的数字。

代码(Java):

public class Solution {
    public int thirdMax(int[] nums) {
        int first = nums[0];
        int second = nums[0];
        int third = nums[0];
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > first) {
                third = second;
                second = first;
                first = nums[i];
            } else if (nums[i] != first && (nums[i] > second || second == first)) {
                third = second;
                second = nums[i];
            } else if ((nums[i] != first && nums[i] != second) && (nums[i] > third || third == second || third == first)) {
                third = nums[i];
            }
        }
        if (first > second && second > third) return third;
        else return first;
    }
}

回到目录


问题:

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

Example:

Given nums = [-2, 0, 3, -5, 2, -1]

sumRange(0, 2) -> 1 sumRange(2, 5) -> -1 sumRange(0, 5) -> -3

Note:

  1. You may assume that the array does not change.
  2. There are many calls to sumRange function.

大意:

给出一个整型数组 nums,寻找其中位置 i 与 j 之间元素的和,包括 i 与 j 元素。

例子:

给出 nums = [-2, 0, 3, -5, 2, -1]

sumRange(0, 2) -> 1 sumRange(2, 5) -> -1 sumRange(0, 5) -> -3

注意:

  1. 你可以假设数组不会变。
  2. 会多次调用 sumRange 函数。

思路:

这道题其实不是考某种算法,而是考实现的方式。题目给出了一个初始化函数一个计算和的函数,如下:

public class NumArray {
    
    public NumArray(int[] nums) {
        
    }

    public int sumRange(int i, int j) {
        
    }
}

一般的实现方法很直接,定义一个变量 nums 数组,在初始化函数中赋值,在求和函数中直接遍历计算就行了很简单。但是如果直接这样做,答案会超时。题目明确指出求和函数会被多次调用,因此这里应该尽量简化求和函数,而把复杂度放在初始化时。

我们在初始化时,直接将每个元素的值改为从第一个元素到当前元素的和,这样在初始化时遍历计算一次。然后在求和时,只需要很简单地用两个位置的值相减就可以得出中间元素的和了。

代码(Java):

public class NumArray {
    int[] nums;
    
    public NumArray(int[] nums) {
        for (int i = 1; i < nums.length; i++)
            nums[i] += nums[i-1];
        
        this.nums = nums;
    }

    public int sumRange(int i, int j) {
        if (i == 0) return nums[j];
        else return nums[j] - nums[i-1];
    }
}

回到目录


问题:

Count the number of prime numbers less than a non-negative number, n.

大意:

计算小于非负数n的质数个数。

思路:

题目很简短,就是个找质数的问题。

我们知道最简单的质数就是2,3,5。。。那怎么计算往后的质数呢?质数的定义是除了自己以外没有任何因子,也就是不被任何数整除,也就是说,不会被这个数前面的任何质数和非质数整除,其实非质数也可以被质数整除,比如4被2整除,所以问题可以归结为:没遇到一个数,判断它是否能被前面的某一个质数整除。

要达到这个条件,我们需要记录在遍历过程中遇到的质数,所以弄了一个数组来记录遇到过的质数,当然2、3、5是一开始就要放进来的,我们遍历时也应该从2开始,因为0和1不是质数。然后没遇到质数都记录下来,同时遇到的每个数都去试一试能不能被前面的质数整除。

这种做法在数字不大的情况下是奏效的,但是当数字大了以后,就会超时了。还是先看代码吧。

代码(Java):

public class Solution {
    public int countPrimes(int n) {
        int result = 0;
        int num = 3;
        int[] prime = new int[n+3];
        prime[0] = 2;
        prime[1] = 3;
        prime[2] = 5;
        for (int i = 2; i < n; i++) {
            if (i < 6 && i != 4) result ++;
            boolean isPrime = true;
            for (int j = 0; j < num; j++) {
                if (i % prime[j] == 0) isPrime = false;
            }
            if (isPrime) {
                result++;
                prime[num] = i;
                num++;
            }
        }
        return result;
    }
}

他山之石:

public class Solution {
    public int countPrimes(int n) {
        int result = 0;
        boolean[] notPrime = new boolean[n];
        for (int i = 2; i < n; i++) {
            if (notPrime[i] == false) {
                result ++;
                for (int j = 2; j*i < n; j++) {
                    notPrime[j*i] = true;
                }
            }
        }
        return result;
    }
}

这里其实还是那个思路,但是加快了速度,为什么呢?因为只需要在每次遇到质数时,将其小于n的倍数都设为非质数,这样就避免了每次遇到一个数都要用之前所有质数去除一遍,大大降低了时间复杂度。

回到目录


问题:

Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.

  • push(x) -- Push element x onto stack.
  • pop() -- Removes the element on top of the stack.
  • top() -- Get the top element.
  • getMin() -- Retrieve the minimum element in the stack.

Example:

MinStack minStack = new MinStack();

minStack.push(-2);

minStack.push(0);

minStack.push(-3);

minStack.getMin(); --> Returns -3.

minStack.pop();

minStack.top(); --> Returns 0.

minStack.getMin(); --> Returns -2.

大意:

设计一个栈,支持push、pop、top以及在固定时间内检索最小元素。

  • push(x) -- 将元素x放入栈中。
  • pop() -- 从栈的顶端移除元素。
  • top() -- 获取栈顶元素。
  • getMin() -- 检索栈中的最小元素。

例子:

MinStack minStack = new MinStack();

minStack.push(-2);

minStack.push(0);

minStack.push(-3);

minStack.getMin(); --> Returns -3.

minStack.pop();

minStack.top(); --> Returns 0.

minStack.getMin(); --> Returns -2.

思路:

这道题对于栈的操作本身都不难,并不是要用队列或者别的数据结构来模拟一个栈,直接就允许用栈来做。真正难的在于检索最小元素,并且还要再固定时间内。

要在入栈以及出栈的同时不停地计算最小元素,首先栈本身是不支持的,如果用数组之类的来记录,那么每次有新元素进来都需要排个序,非常耗时。

因此,这种方法巧妙地在每次入栈、出栈时进行简单的加减操作,达到可以直接获取最小元素的结果。在入栈时,入的不是实际的元素值,而是与当前记录的最小值的差值,如果新入的更小,就将其设为最小值,此时就保证了随时可以获取最小值。出栈时,要修改最小值。获取栈顶元素时,因为栈中记录的并不是原始值,所以要与记录的最小值进行操作来还原。

由于题目在submit时会用超过int范围的大数来测试,所以只能用long来操作。

他山之石(Java):

public class MinStack {
    Stack<Long> stack;
    long min;

    /** initialize your data structure here. */
    public MinStack() {
        stack=new Stack<Long>();
    }
    
    public void push(int x) {
        if (stack.isEmpty()) {
            stack.push(0L);
            min = x;
        } else {
            stack.push(x - min);
            if (x < min) min = x;
        }
    }
    
    public void pop() {
        if (stack.isEmpty()) return;
        
        long pop = stack.pop();
        if (pop < 0) min = min - pop;
    }
    
    public int top() {
        long top = stack.peek();
        if (top < 0) return (int)min;
        else return (int)(min + top);
    }
    
    public int getMin() {
        return (int)min;
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

回到目录


问题:

The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

P A H N

A P L S I I G

Y I R

And then read line by line: "PAHNAPLSIIGYIR"

Write the code that will take a string and make this conversion given a number of rows:

string convert(string text, int nRows);

convert("PAYPALISHIRING", 3) should return "PAHNAPLSIIGYIR".

大意:

字符串 "PAYPALISHIRING" 被根据给出的数字以 zigzag 模式写成了下面的样子:(你可能想要用固定的字形来达到更好的可读性)

P A H N

A P L S I I G

Y I R

一行行读起来就是:"PAHNAPLSIIGYIR"

写代码获取字符串并根据给出的行数作出这种转换:

string convert(string text, int nRows);

convert("PAYPALISHIRING", 3) 应该 return "PAHNAPLSIIGYIR".

思路:

这道题的意思就题目来看其实不太好理解,这里借用别人画的一张图就能一目了然了:

如图,行数就是给出的数字,按照图中的数字顺序将原本的字符串中一个个字符去按照循环对角的结构放置。

而题目要求返回一行行读下来的字符串。

如果找规律去找各个字符所在的位置还比较难的,比较好的方法是根据行数创建多个字符串,然后将给出的字符串中的字符按照顺序去一个个放置,这里就循环地利用两个循环,一个是竖着下来,一个是斜着上去。最后再将这些字符串一个个拼接成一个完整的字符串,就出来了。

代码中我用的是直接 + 的方式来拼接字符串,这个效率其实比较低,如果要提高速度,建议使用StringBuffer来做,但是为了代码可读性,还是用 + 来展示,这个点知道就好了。

代码(Java):

public class Solution {
    public String convert(String s, int numRows) {
        String[] row = new String[numRows];
        for (int k = 0; k < numRows; k++) row[k] = "";
        int i = 0;
        int j = 0;
        int gap = numRows-2;
        while (i < s.length()) {
            for (j = 0; j < numRows && i < s.length(); j++) row[j] += s.charAt(i++);
            for (j = gap; j > 0 && i < s.length(); j--) row[j] += s.charAt(i++);
        }
        String result = "";
        for (int k = 0; k < numRows; k++) result += row[k];
        return result;
    }
}

回到目录


问题:

Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.

For example,

"A man, a plan, a canal: Panama" is a palindrome.

"race a car" is not a palindrome.

Note:

Have you consider that the string might be empty? This is a good question to ask during an interview.

For the purpose of this problem, we define empty string as valid palindrome.

大意:

给出一个字符串,判断它是不是回文,只考虑大小写字母和数字,忽略大小写。

例子:

"A man, a plan, a canal: Panama" 是回文。

"race a car" 不是回文。

注意:

你有考虑字符串可能为空吗?这是面试时的一个好问题。

对于这道题的目的,我们假设空字符串也是有效的回文。

思路:

又是一道判断回文的题目,不同的是这道题只判断字符串中的大小写字母和数字,从例子中也可以看出,空格和其他标点符号都跟没看到一样,也就是在做的时候要忽略,另外大小写字母忽略,看做是相同的,这也就意味着在判断是否相同时要将大小写字母转为同一个格式。

由于要先判断一个字符是不是数字或者大小写字母,我们做一个方法来专门检测这个事情,避免主体代码太冗长。

在主体代码中,我们用两个指针,一个从头开始遍历,一个从末尾开始遍历,当头尾都找到字母或者数字后,就进行对比是否是相同的,有不同说明不是回文,否则就是回文,在比较时我们将大写字母都转化成小写来对比,当然也可以反过来。

代码(Java):

public class Solution {
    public boolean isLetterOrDigit(char test) {
        if ((test - '0' >= 0 && test - '0' <= 9) || (test - 'a' >= 0 && test - 'a' <= 25) || (test - 'A' >= 0 && test - 'A' <= 25)) return true;
        else return false;
    }
    
    public boolean isPalindrome(String s) {
        if (s.length() == 0) return true;
        
        int i = 0;
        int k = s.length() - 1;
        
        while (i <= k) {
            char tempI = s.charAt(i);
            char tempK = s.charAt(k);
            
            if (!isLetterOrDigit(tempI)) i++;
            else if (!isLetterOrDigit(tempK)) k--;
            else {
                if (Character.toLowerCase(tempI) != Character.toLowerCase(tempK)) return false;
                else {
                    i++;
                    k--;
                }
            }
        }
        return true;
    }
}

回到目录


问题:

Given a positive integer, return its corresponding column title as appear in an Excel sheet.

For example:

1 -> A 2 -> B 3 -> C ... 26 -> Z 27 -> AA 28 -> AB

大意:

给出一个正整数,返回它对应的Excel表格中的列标题。

例子:

1 -> A 2 -> B 3 -> C ... 26 -> Z 27 -> AA 28 -> AB

思路:

我们找一下规律,一个字母的有26列,两个字母的从AA到ZZ总共有2626列,三个字母的总共有2626*26列。因此我们要找出对应的列标题,只需要根据给出的数字来一位一位地找对应的字母就可以了。

比如给出的数字对26取余数得到的数字,就是列标题的最后一个字母的序号。将给出的数字除以26,再取余,就可以得到倒数第二位的字母序号,一直到什么时候为止呢,到除以26为0打止,这时候已经没有更高位的字母了。

其实可以理解为将一个十进制的数字转换成一个26进制的数字,跟转换成二进制是一个道理。

注意我们要得到数字需要使用ASCII对应的数字来进行转换,A对应的码是65,所以我们每次转换时都要将余数+65来转换成字符,但是如果是对应的Z,其实除以26是没有余数的,因此在每次取余之前,都要将n减一。

同样的,在字符串拼接时使用 + 拼接是很慢的,换成StringBuffer来拼接会快很多。

代码(Java):

public class Solution {
    public String convertToTitle(int n) {
        String result = "";
        while (n > 0) {
            n--;
            result = String.valueOf((char)(n%26 + 65)) + result;
            n = n / 26;
        }
        
        return result;
    }
}

回到目录


问题:

You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.

Suppose you have n versions [1, 2, ..., n] and you want to find out the first bad one, which causes all the following ones to be bad.

You are given an APIwhich will return whether version is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.

大意:

你是一个产品经理,现在领导一个小组来开发一个新产品。不幸的是,你产品最近的版本没通过质检。每个版本都是基于前一个版本开发的,在坏的版本后的所有版本都是坏的。

假设你有n个版本 [1, 2, ..., n] 并且想要找到第一个坏的,也就是导致后面都坏的那个。

给你一个API, bool isBadVersion(version) ,可以返回一个版本是不是坏的。实现一个函数来找到最开始IDE坏版本。你应该最小化API的调用次数。

思路:

说了这么多,归根结底就是个快速查找的问题。

快速查找的方法很多,这里使用二分法最简单。

因为每个坏的版本后面一定都是坏的,所以可以通过判断中间的版本是不是好的来决定是要继续检查前面一半还是后面一半。如果检查中间的版本是坏的,就在前面一半继续检查中间的版本,否则在后面一半继续检查。

这里我用递归做,也可以用循环来做,都需要创建一个开始和一个结束的变量,才能进行二分法。

注意在查看坏的之后,要判断它前一个版本是否是好的,这样才能找到第一个坏的,但是还要注意如果这是第一个版本,直接就要返回了,因为没有更早的版本了。

注意题目的测试用例会用大超过int的大数,所以我使用了long。

代码(Java):

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        return (int)findBad(1l, (long)n);
    }
    
    public long findBad(long start, long end) {
        if (start == end) return start;
        if (isBadVersion((int)((start+end)/2))) {// 当前查找的是坏的,检查前面的一个数是不是好的,或者当前数是否为1
            if ((start+end)/2 == 1 || !isBadVersion((int)((start+end)/2-1))) return (start+end)/2;
            else return findBad(start, (start+end)/2-1);
        } else return findBad((start+end)/2+1, end);
    }
}

回到目录


问题:

Reverse digits of an integer.

Example1: x = 123, return 321

Example2: x = -123, return -321

大意:

反转一个整型数的数字。

例1:x = 123, return 321

例2:x = -123, return -321

思路:

题目很简洁,注意是有负数的。

我的方法比较直接,先转成String,有负号就保留负号,然后一个从尾部往前取数字,从新字符串的头部往后放,就反转过来了。最后再转成int型返回。

这里使用了StringBuffer来加快拼接字符串的速度,不过依然没有别人的方法快。

注意题目很无聊的会传超出int范围的数字给你测试,明明参数写明了是int型的还给超大数,那就只能做一个try-catch,如果是超大数转换失败,那就直接返回0。

代码(Java):

public class Solution {
    public int reverse(int x) {
        String xStr = String.valueOf(x);
        String reverse = "";
        int start = 0;
        if (xStr.charAt(0) == '-') {
            reverse = "-";
            start = 1;
        }
        StringBuffer reverseBuffer = new StringBuffer(reverse);
        for (int i = xStr.length()-1; i >= start; i--) {
            reverseBuffer.append(xStr.charAt(i));
            reverse = reverse + xStr.charAt(i);
        }
        reverse = reverseBuffer.toString();
        try {
            return Integer.valueOf(reverse).intValue();
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}

他山之石:

public class Solution {
	public int reverse(int x)
	{
	    int result = 0;
	
	    while (x != 0)
	    {
	        int tail = x % 10;
	        int newResult = result * 10 + tail;
	        if ((newResult - tail) / 10 != result)
	        { return 0; }
	        result = newResult;
	        x = x / 10;
	    }
	
	    return result;
	}
}

这个做法是直接左数字计算,每次取余得到最末尾的数字,取出来之后原数字除以10,取出来的数字加到新数字末尾去,不过新数字要先乘以10,也就是所有数字提高一位。他中间有个判断 (newResult - tail) / 10 != result ,其实也是为了防止超大数溢出,如果溢出了就返回0。

回到目录


问题:

Rotate an array of n elements to the right by k steps.

For example, with n = 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4].

大意:

旋转一个有n个元素的数组,右移k步。

比如,n = 7,k = 3,数组 [1,2,3,4,5,6,7] 就被旋转为 [5,6,7,1,2,3,4]。

思路:

旋转本身没太多特别好说的,我的做法是用另一个数组来记录旋转后的内容,然后复制回原数组。当然记录时是从第nums.length-k个元素开始记录,记录到末尾后再去从头开始记录到刚才那个元素。

需要注意的是这里并非返回一个数组,程序会直接读取原数组位置的内容来检查,所以需要把旋转后的结果一个个复制回去。

还有,给出的k可能会大于数组长度,这时候就要对原数组长度取模,才会得出真正需要旋转的个数。

代码(Java):

public class Solution {
    public void rotate(int[] nums, int k) {
        k %= nums.length;
        int[] rotateNums = new int[nums.length];
        int index = 0;
        for (int i = nums.length-k; i < nums.length; i++) {
            rotateNums[index] = nums[i];
            index++;
        }
        for (int i = 0; i < nums.length-k; i++) {
            rotateNums[index] = nums[i];
            index++;
        }
        for (int i = 0; i < nums.length; i++) {
            nums[i] = rotateNums[i];
        }
        return;
    }
}

他山之石:

public void rotate(int[] nums, int k) {

    if(nums == null || nums.length < 2){
        return;
    }
    
    k = k % nums.length;
    reverse(nums, 0, nums.length - k - 1);
    reverse(nums, nums.length - k, nums.length - 1);
    reverse(nums, 0, nums.length - 1);
    
}

private void reverse(int[] nums, int i, int j){
    int tmp = 0;       
    while(i < j){
        tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
        i++;
        j--;
    }
}

这里他用了三次不同的翻转函数,先翻转第一到要旋转的元素之前的元素,然后翻转要旋转的那些元素,最后翻转所有元素。这么一个过程之后就神奇地得到了正确的结果。

回到目录


问题:

Compare two version numbers version1 and version2.

If version1 > version2 return 1, if version1 < version2 return -1, otherwise return 0.

You may assume that the version strings are non-empty and contain only digits and the . character.

The . character does not represent a decimal point and is used to separate number sequences.

For instance, 2.5 is not "two and a half" or "half way to version three", it is the fifth second-level revision of the second first-level revision.

Here is an example of version numbers ordering:

0.1 < 1.1 < 1.2 < 13.37

大意:

比较两个版本号 version1 和 version2。

如果 version1 > version2 就返回 ,如果 version1 < version2 就返回 -1,否则返回0。

你可以假设版本字符串是非空的而且只包含数字和 “.” 字符。

“.”字符不表示小数点,是用来区分数字序号的。

比如说,2.5不是指超过2一半或者还有一半到版本3,而是第二个版本的第五次迭代。

这里是一个版本号顺序的例子:

0.1 < 1.1 < 1.2 < 13.37

思路:

比较版本号实在系统开发中经常用到的东西,题目中的例子给的其实有点不靠谱,版本号真正特殊的地方在于会存在多个小数点,也就是会有 1.3.2 这种版本号存在,真正要判断大小的也是这类版本。

我们首先把两个版本号字符串用 split 函数根据小数点分割成数组,注意要根据小数点区分不能直接只写小数点,还要用转义符,否则无法分割,具体看下面的代码。

然后依次比较每位数字的大小,只要出现同一个位置上某一方更大,就可以直接返回结果了。需要注意的是不能直接比较字符串,需要转成int去比较,因为题目会给出“000”这种多个0的情况,字符串直接比较不了,转成int后就都是0了。

当某个版本号的数字全部比较完后,看看另一个版本号还有没有内容,有的话也不能急着判断是另一个大,因为存在 1.0 这种情况,它和 1 是一样大的,就是多一位0,那是不是只用判断下一位是不是0呢,也不是,还有 1.0.1 的情况,它就比 1 大。所以,当某个版本号还有剩余的时候,我们要看看剩余的部分有没有不是0的部分,这里同样必须转成int去和0比较,字符串无法全部囊括。

自己测试下面几个用例就差不多了:

  • 1.1 和 1.2
  • 1 和 1.0
  • 1 和 1.0.1
  • 1.00.0 和 1.0
  • 1 和 1.00

代码(Java):

public class Solution {
    public int compareVersion(String version1, String version2) {
        String[] arr1 = version1.split("\\.");
        String[] arr2 = version2.split("\\.");
        // System.out.println(arr1.length + " " + arr2.length);
        int i = 0;
        while (i < arr1.length && i < arr2.length) {
            // System.out.println(arr1[i] + " " + arr2[i]);
            int a = Integer.valueOf(arr1[i]).intValue();
            int b = Integer.valueOf(arr2[i]).intValue();
            if (a > b) return 1;
            else if (a < b) return -1;
            
            i++;
        }
        if (i < arr1.length) {
            while (i < arr1.length) {
                int version = Integer.valueOf(arr1[i]).intValue();
                if (version != 0) return 1;
                i++;
            }
            return 0;
        } 
        else if ( i < arr2.length) {
            while (i < arr2.length) {
                int version = Integer.valueOf(arr2[i]).intValue();
                if (version != 0) return -1;
                i++;
            }
            return 0;
        }
        else return 0;
    }
}

回到目录


###问题:

Implement atoi to convert a string to an integer.

Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below and ask yourself what are the possible input cases.

Notes: It is intended for this problem to be specified vaguely (ie, no given input specs). You are responsible to gather all the input requirements up front.

###大意:

实现 atoi 来将一个字符串转为一个整型。

暗示:小心考虑所有可能的输入案例。如果你想要挑战,请不要看下面的内容并问问你自己有哪些可能的输入案例。

注意:这个问题特意说的比较模糊(比如,没有给出输入规则)。你需要收集所有输入需求。

###思路:

字符串转换成整型本身不难,麻烦的在于考虑到所有可能的情况并作出处理。

可能有的情况有:

  • -开头的负数
  • +开头的正数
  • 0 开头的数字
  • 空格开始的数字
  • 数字直接开头
  • 除了以上的情况外其余开头的都返回0
  • 数字中间夹了其余非数字字符的话就不考虑后面的内容了
  • 基于以上考虑,我的代码考虑了很多情况并进行处理。

###代码(Java):

public class Solution {
    public int myAtoi(String str) {
        String intStr = "";
        char[] tempArr = str.toCharArray();
        char[] arr = new char[tempArr.length];
        int num = 0;
        int j = 0;
        boolean ok = false;
        for (; num < tempArr.length; num++) {
            if (ok || tempArr[num] - ' ' != 0) {
                ok = true;
                arr[j] = tempArr[num];
                j++;
            }
        }

        if (arr.length == 0 || !((arr[0] - '0' <= 9 && arr[0] - '0' >= 0) || arr[0] - '-' == 0 || arr[0] - '+' == 0)) return 0;
        int i = 0;
        if (arr[0] - '+' == 0) i = 1;
        for (; i < arr.length; i++) {
            if ((arr[i] - '0' <= 9 && arr[i] - '0' >= 0) || (i == 0 && arr[i] - '-' == 0)) {
                intStr = intStr + arr[i];
            } else break;
        }
        if (intStr.length() == 0) return 0;
        else if (intStr.equals("-")) return 0;
        else if (i > 11) return intStr.charAt(0) - '-' == 0 ? -2147483648 : 2147483647;
        else if (Long.parseLong(intStr) > 2147483647) return 2147483647;
        else if (Long.parseLong(intStr) < -2147483648) return -2147483648;
        else return Integer.valueOf(intStr).intValue();
    }
}

回到目录


问题:

TheHamming distancebetween two integers is the number of positions at which the corresponding bits are different.

Given two integersxandy, calculate the Hamming distance.

Note:

0 ≤x,y< 231. Example:

Input: x = 1, y = 4

Output: 2

Explanation:

The above arrows point to positions where the corresponding bits are different.

大意:

两个数之间的汉明距离是指其比特位的不同的个数。

给出两个整数x和y,计算汉明距离。

注意:

0 ≤x,y< 231.

例子:

输入: x = 1, y = 4

输出: 2

解释:

上面的箭头指向的位置就是比特位不同的地方。

思路

题目很简单,就是比较两个数字的比特位不同之处,那就从右到左一个个比较就好了,也就是要从右往左一个个比较每比特位数字是否相同。因为实际上整数在计算机中就是以二进制存储的,所以可以用右移操作符来操作,更直白的也可以不断地除以2来右移,同时对2取模来得到每次最右边的那个数字,看看两个数取模得到的数是否一样,不一样则距离加一。需要注意的就是每次除以2时要判断数字是否已经变成0了,同样的方式判断循环是否终止。

代码(C++):

class Solution {
public:
    int hammingDistance(int x, int y) {
        int x1,y1,res = 0;
        while (x > 0 || y > 0) {
            x1 = x % 2;
            y1 = y % 2;
            if (x1 != y1) res++;
            
            if (x > 0) x = x / 2;
            if (y > 0) y = y / 2;
        }
        return res;
    }
};

他山之石:

用异或这个位操作符就可以记录下两个数字所有比特位不同的地方,然后同样的一个个去记录不同的个数,这里他用了一种很巧妙的方式,自己距离推演一下就知道怎么有效的了。

class Solution {
public:
    int hammingDistance(int x, int y) {
        int dist = 0, n = x ^ y;
        while (n) {
            ++dist;
            n &= n - 1;
        }
        return dist;
    }
};

回到目录


问题:

Initially, there is a Robot at position (0, 0). Given a sequence of its moves, judge if this robot makes a circle, which means it moves back to the original place.

The move sequence is represented by a string. And each move is represent by a character. The valid robot moves are R (Right), L (Left), U (Up) and D (down). The output should be true or false representing whether the robot makes a circle.

Example 1:

Input: "UD"

Output: true

Example 2:

Input: "LL"

Output: false

大意:

一开始,有一个机器在(0,0)的位置。给出一个移动序列,判断机器是否运动了一个环,所谓环就是运动回了初始位置。

移动序列由字符串表示。每次移动由一个字符表示。有效的机器移动是R(右)、L(左)、U(上)和D(下)。输出应该是true和false来表示机器是否运动了一个环。

例1:

输入:“UD”

输出:true

例2:

输入:“LL”

输出:false

思路:

题目已经指出了做法的关键——坐标。定义x、y两个坐标,初始为0,根据上下左右的移动方式来修改x和y的值,最后看x和y是不是还是0,如果是则是回到了原味了。

需要注意的是,虽然没尝试,但看题目的意思应该是有陷阱在于输入的字符串不仅仅包含着四个字母,还可能有别的,所以要对这四个字母专门判断(else if),不能随便用个else。还有就是C++创建int变量并不会默认初始化为0,而是一个最大值,需要手动初始化为0。

代码(C++):

class Solution {
public:
    bool judgeCircle(string moves) {
        int x = 0,y = 0;
        for (int i = 0; i < moves.size(); i++) {
            if (moves[i] == 'U') y++;
            else if (moves[i] == 'D') y--;
            else if (moves[i] == 'L') x--;
            else if (moves[i] == 'R') x++;
        }
        // std::cout << "x:" << x << " y:" << y << std::endl;
        if (x == 0 && y == 0) return true;
        else return false;
    }
};

回到目录


问题:

A self-dividing number is a number that is divisible by every digit it contains.

For example, 128 is a self-dividing number because 128 % 1 == 0, 128 % 2 == 0, and 128 % 8 == 0.

Also, a self-dividing number is not allowed to contain the digit zero.

Given a lower and upper number bound, output a list of every possible self dividing number, including the bounds if possible.

Example 1:

Input:

left = 1, right = 22

Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 22]

Note:

The boundaries of each input argument are 1 <= left <= right <= 10000.

大意:

一个自分数是指能够被自己包含的每个数字整除的数。

比如说,128就是个自分数,因为 128 % 1 == 0, 128 % 2 == 0, 并且 128 % 8 == 0。

同时,一个自分数不允许包含数字0。

给出一个数字区间,输出其中所有自分数的清单,包含区间两端的数。

例1:

输入:

left=1,right=22;

输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 22]

注意:

每个输入参数的范围为 1 <= left <= right <= 10000。

思路:

只需要遍历区间内所有数字,对每个数依次得到每一位的数字,然后试试能不能整除就可以了,很简单,这里我把判断的代码写到另一个函数里,看起来更清晰。

本来还考虑了如果区间包含负数和0该怎么办,后来发现题目说明了都是正数,那就更容易了。

代码(C++):

class Solution {
public:
    bool isselfDividing(int num) {
        if (num == 0) return false;
        int origin = num;
        while (abs(num)  > 0) {
            int temp = num % 10;
            if (temp == 0) return false;
            if (origin % temp != 0) return false;
            num = num / 10;
        }
        return true;
    }
    
    vector<int> selfDividingNumbers(int left, int right) {
        vector<int> res;
        for (int i = left; i <= right; i++) {
            if (isselfDividing(i)) res.push_back(i);
        }
        return res;
    }
};

回到目录


问题:

Given two binary trees and imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not.

You need to merge them into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree.

Example 1:

Input:

Output:

Merged tree:

Note: The merging process must start from the root nodes of both trees.

大意:

给出两个二叉树,想象用一个来覆盖另一个,两棵树中有些节点位置会重叠,有些不会。

你需要将它们合并到一个新二叉树。合并规则是如果两个节点重叠了,结果节点的值是两个节点值的和,如果没重叠,就取其中非null的节点作为新树的节点。

例1:

输入:

输出:

合并成的树:

注意:合并过程必须从两棵树的根节点开始。

思路:

从两个根节点开始,先判断根节点是否为空,都不为空则利用递归,将根节点的值相加,然后判断左右子节点是否分别为空,有一个为空则直接取另一个节点,都不为空则递归处理。

这里我们总是以第一颗树作为返回的新树,所以如果要相加节点值,则都加到第一颗树节点上,如果第二颗树的节点为null,则直接取第一颗树的节点,如果第一颗树的节点为null,则将第二颗树的节点复制到第一颗树来,需要注意的树不能直接让节点本身做赋值,要将节点赋值给父节点的子节点指针才算是建立了树关系。

代码(C++):

/**
 * 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 merge(TreeNode *t1, TreeNode *t2) {
        t1->val += t2->val;
        
        if (t1->left == NULL) t1->left = t2->left;
        else 
            if (t2->left != NULL) merge(t1->left, t2->left);
        
        if (t1->right == NULL) t1->right = t2->right;
        else 
            if (t2->right != NULL) merge(t1->right, t2->right);
        
    }
    
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == NULL) return t2;
        if (t2 == NULL) return t1;
        
        merge(t1, t2);
        
        return t1;
    }
};

回到目录


问题:

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:

  1. n is a positive integer, which is in the range of [1, 10000].
  2. All the integers in the array will be in the range of [-10000, 10000].

大意:

给出一个包含2n个整数的数组,你的任务是将这些整数组合成n对,成为 (a1, b1), (a2, b2), ..., (an, bn),使n组队的min(ai, bi)之和最大。

例1:

输入:[1,4,3,2]

输出:4

解释:n是2,最大和的对是 4 = min(1, 2) + min(3, 4)。

注意:

  1. n是个正数,范围在[1, 10000]。
  2. 数组中所有正数的范围都在[-10000, 10000]。

思路:

题目的意思就是2n个数分成n对,取每一对中较小的数相加,令和最大。要达到和最大,也就是要让每一对的较小数尽量大,怎么处理呢?只要确定每一对都是数组中大小相邻的两个数就可以了,比如例子中是1/2/3/4,那么就要相邻的1/2为一对,3/4为一对,这样才有可能把3留下来。这种操作的目的是尽量让大数有可能被算到和里去。

因此要达到这个目的,需要先把数组排序,然后隔一个数取一个数算到最终和里去就可以了。

这样做的时间复杂度是排序算法的时间复杂度,但是必须要确定整数的顺序,有的做法用空间换时间(题目明确了整数范围,因此可以确定空间大小)来达到以O(n)时间复杂度排序的目的,但是没太多必要其实。

代码(C++):

class Solution {
public:
    int arrayPairSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int res = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (i % 2 == 0) res = res + nums[i];
        }
        return res;
    }
};

回到目录


问题:

Given a string, you need to reverse the order of characters in each word within a sentence while still preserving whitespace and initial word order.

Example 1:

Input: "Let's take LeetCode contest"

Output: "s'teL ekat edoCteeL tsetnoc"

Note: In the string, each word is separated by single space and there will not be any extra space in the string.

大意:

给出一个字符串,你需要翻转句子中每个单词的字母,但保证空格位置以及原始的单词顺序不变。

例1:

输入:"Let's take LeetCode contest"

输出: "s'teL ekat edoCteeL tsetnoc"

注意:在字符串中,每个单词都被单个空格分开,不会有额外的空格。

思路:

遍历字符串,没遇到一个空格就开始处理前面的这个单词,将其用一些方式进行反转后存入新的字符串中,然后记得在新字符串后面加个空格(最后一个单词就不要加空格了)。

如何对单词反转有多种方式,可以用一个临时容器来存储,遇到单词中每个字母都将其插入到容器首部,然后再将整个容器的内容放到字符串中就好了。这个容器可以是deque这种允许两端插入的,也可以就是string。但是用string(49ms)居然比用在首部插入应该更快的deque(768ms)要快得多。

代码(C++):

// 用deque
class Solution {
public:
    string reverseWords(string s) {
        deque<char> que;
        string res = "";
        for (int i = 0; i < s.length(); i++) {
            if (s[i] != ' ') {
                que.push_front(s[i]);
            } else {
                auto iter = que.begin();
                while (iter != que.end()) {
                    res = res + *iter;
                    iter++;
                }
                que.clear();
                res = res + " ";
            }
        }
        auto iter = que.begin();
        while (iter != que.end()) {
            res = res + *iter;
            iter++;
        }
        return res;
    }
};

// 用string
class Solution {
public:
    string reverseWords(string s) {
        string res = "";
        int pos = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s[i] != ' ') {
                res.insert(pos, s.substr(i, 1));
            } else {
                res = res + " ";
                pos = i + 1;
            }
        }
        return res;
    }
};

他山之石:

原来C++可以直接操作让string的部分区间进行反转,那就只需要记录空格的位置,然后将之间的区域进行反转就行了,也不用创建结果字符串,直接在原字符串上操作即可,速度快了一倍。

class Solution {
public:
    string reverseWords(string s) {
        for (int i = 0; i < s.length(); i++) {
            if (s[i] != ' ') {   // when i is a non-space
                int j = i;
                for (; j < s.length() && s[j] != ' '; j++) { } // move j to the next space
                reverse(s.begin() + i, s.begin() + j);
                i = j - 1;
            }
        }
        
        return s;
    }
};

回到目录


问题:

Given a List of words, return the words that can be typed using letters ofalphabet on only one row's of American keyboard like the image below.

Example 1:

Input: ["Hello", "Alaska", "Dad", "Peace"]

Output: ["Alaska", "Dad"]

Note:

  1. You may use one character in the keyboard more than once.
  2. You may assume the input string will only contain letters of alphabet.

大意:

给出一系列单词,返回可以只用如下的美式键盘中一行字母打印出来的单词。

例1:

输入:["Hello", "Alaska", "Dad", "Peace"]

输出:["Alaska", "Dad"]

注意:

  1. 你可以使用一个字母多次。
  2. 你可以假设输入只包含字母表的字母。

思路:

既然题目说只包含字母,那我们就用一个大小为26数组来记录每个字母在第几行。然后遍历容器,对于每个字符串,看看其中每个字母属于哪一行,这里要注意字母有大小写之分。为了方便,我们可以用一个变量来保存一个字符串中的字母的行,如果在遍历字母过程中出现了不一样的行,那就视为要剔除的字符串,否则就保留,这里我们可以用容易的删除操作,不用创建新容器来保存数据。这样做的速度最快。

代码(C++):

class Solution {
public:
    vector<string> findWords(vector<string>& words) {
        int rows[] = {2,3,3,2,1,2,2,2,1,2,2,2,3,3,1,1,1,1,2,1,1,3,1,3,1,3};
        auto iter = words.begin();
        while (iter != words.end()) {
            string str = *iter;
            int row = 0;
            bool pass = true;
            for (int i = 0; i <str.length(); i++) {
                int index = 0;
                if (str[i] - 'a' < 0) index = str[i] - 'A';
                else index = str[i] - 'a';
                
                if (row == 0) row = rows[index];
                else if (rows[index] != row) {
                    pass = false;
                    break;
                }
            }
            if (pass) iter++;
            else iter = words.erase(iter, iter+1);
        }
        return words;
    }
};

回到目录


问题:

Given a binary search tree and the lowest and highest boundaries as L and R, trim the tree so that all its elements lies in [L, R] (R >= L). You might need to change the root of the tree, so the result should return the new root of the trimmed binary search tree.

Example 1:

Input:

Output:

Example 2:

Input:

Output:

大意:

给出一个二叉查找树和最小、最大边界L和R,修剪树使其所有元素都在[L, R](R >= L)中。你可能需要改变树的根节点,所以结果应该返回裁剪后的树的新跟节点。

例1:

输入:

输出:

例2:

输入:

输出:

思路:

题目的意思就是让树只保留L到R范围内的数字,但是还是要保证树是个二叉查找树,虽然题目说了可能要改变根节点,但实际上只有根节点不在范围内的时候才需要更改。

思路就是递归遍历树的每个节点,将其看做根节点,判断是否为空、是否在范围内。如果不在,则要根据大小取左子节点或者右子节点作为新的根节点;如果在,则继续判断左右子节点。就直接用递归来做。

要注意每次递归都会在一开始就判断根节点是否为空,所以不用在递归前又去判断子节点是否为空,实测这将节省大量时间

代码:

/**
 * 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:
    TreeNode* trimBST(TreeNode* root, int L, int R) {
        if (root == NULL) return NULL;
        
        TreeNode *res = root;
        if (root->val > R) 
            res = trimBST(root->left, L, R);
        else if (root->val < L) 
            res = trimBST(root->right, L, R);
        else {
            res->left = trimBST(res->left, L, R);
            res->right = trimBST(res->right, L, R);
        }
        
        return res;
        
    }
};

他山之石:

除了用递归,也可以用循环来做,就循环判断左右子树,看大小是否在范围内,在则继续往下,否则将其左/右子节点作为当前循环的节点。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int L, int R) {
        
       // find the proper root
        while(root->val<L || root->val>R)
        {
            if(root->val<L) { root = root->right; }
            else { root = root->left; }
        }
        
        // temporary pointer for left and right subtree
        TreeNode *Ltemp = root;
        TreeNode *Rtemp = root;
        
        // remove the elements larger than L
        while(Ltemp->left)
        {
            if( (Ltemp->left->val)<L ) { Ltemp->left = Ltemp->left->right; }
            else { Ltemp = Ltemp->left; }
        }
         // remove the elements larger than R
        while(Rtemp->right)
        {
            if( (Rtemp->right->val)>R) { Rtemp->right = Rtemp->right->left; }
            else { Rtemp = Rtemp->right; }
        }

        return root;
    }
};

回到目录


问题:

Given an integer array with even length, where different numbers in this array represent different kinds of candies. Each number means one candy of the corresponding kind. You need to distribute these candies equally in number to brother and sister. Return the maximum number of kinds of candies the sister could gain.

Example 1:

Input: candies = [1,1,2,2,3,3]

Output: 3

Explanation:

There are three different kinds of candies (1, 2 and 3), and two candies for each kind.

Optimal distribution: The sister has candies [1,2,3] and the brother has candies [1,2,3], too.

The sister has three different kinds of candies.

Example 2:

Input: candies = [1,1,2,3]

Output: 2

Explanation: For example, the sister has candies [2,3] and the brother has candies [1,1].

The sister has two different kinds of candies, the brother has only one kind of candies.

Note:

  1. The length of the given array is in range [2, 10,000], and will be even.
  2. The number in given array is in range [-100,000, 100,000].

大意:

给出一个偶数长度的证书数组,其中不同的数字表示不同类别的糖果。每个数字表示一个不同类别的糖果。你需要将这些糖果平均分给弟弟和妹妹。返回妹妹能得到的最大的糖果种类数。

例1:

输入:candies = [1,1,2,2,3,3]

输出:3

解释:

有三种糖果(1,2,3),每种类别有两个糖果。

可选的分布:妹妹得到糖果[1,2,3],弟弟也得到通过[1,2,3]。

妹妹得到了三种糖果。

例2:

输入:candies = [1,1,2,3]

输出:2

解释:比如,妹妹得到糖果[2,3],弟弟得到糖果[1,1]。

妹妹有两种糖果,而弟弟只有一种。

注意:

  1. 给出数组的长度在[2, 10000],会是偶数。
  2. 给出的数字的范围在[-100000, 100000]。

思路1:

其实就是看有多少个糖果种类,也就是多少个不同的数字,因为糖果一定是均分给两人的,如果种类数大于总数的一半,那妹妹能得到的最大种类数就是总数的一半。如果种类数小于总数的一半,那肯定最多能得到的就是种类数了。

需要注意的是题目并没说给出的数组是排了序的,所以不能直接遍历看种类,需要用一些集合来记录出现过的种类(数字),遇到出现过的就不再记录了,遍历一遍后得到总种类数,再跟总数的一半比大小就知道了。

代码1(C++):

class Solution {
public:
    int distributeCandies(vector<int>& candies) {
        int num = candies.size() / 2;
        int sort = 0;
        map<int, int> maps;
        auto iter = candies.begin();
        while (iter != candies.end()) {
            if (maps.find(*iter) == maps.end()) {
                sort++;
                maps.insert(pair<int, int>(*iter, 1));
            }
            iter++;
        }
        if (sort > num) return num;
        else return sort;
    }
};

其实用set会更加方便一点,不用判断是否出现过,直接放set里放,最后统计set里的元素数,set保证一样的只会留一个。且最后的比较可以用min函数。

代码1改进(C++):

class Solution {
public:
    int distributeCandies(vector<int>& candies) {
        unordered_set<int> kinds;
        for (int kind : candies) {
            kinds.insert(kind);
        }
        return min(kinds.size(), candies.size() / 2);
    }
};

思路2:

前面也说到了排序,如果先给集合排个序,那么就可以遍历集合来统计种类数了,只要遍历时跟前一个比较是否一样即可,这种方式因为要事先排序,时间复杂度会降低为排序的时间复杂度。

代码2(C++):

class Solution {
public:
    int distributeCandies(vector<int>& candies) {
        size_t kinds = 0;
        sort(candies.begin(), candies.end());
        for (int i = 0; i < candies.size(); i++) {
            kinds += i == 0 || candies[i] != candies[i - 1];
        }
        return min(kinds, candies.size() / 2);
    }
};

回到目录


问题:

In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a new one with different size but keep its original data.

You're given a matrix represented by a two-dimensional array, and two positive integers r and c representing the row number and column number of the wanted reshaped matrix, respectively.

The reshaped matrix need to be filled with all the elements of the original matrix in the same row-traversing order as they were.

If the 'reshape' operation with given parameters is possible and legal, output the new reshaped matrix; Otherwise, output the original matrix.

Example 1:

Input:

nums =

[[1,2],

[3,4]]

r = 1, c = 4

Output:

[[1,2,3,4]]

Explanation:

The row-traversing of nums is [1,2,3,4]. The new reshaped matrix is a 1 * 4 matrix, fill it row by row by using the previous list.

Example 2:

Input:

nums =

[[1,2],

[3,4]]

r = 2, c = 4

Output:

[[1,2],

[3,4]]

Explanation:

There is no way to reshape a 2 * 2 matrix to a 2 * 4 matrix. So output the original matrix.

Note:

  1. The height and width of the given matrix is in range [1, 100].
  2. The given r and c are all positive.

大意:

在MATLAB中,有一个很有用的函数名为“reshape”,可以重构一个矩阵为另一个尺寸,并保持原始数据。

给你一个由二维数组表示的矩阵,和两个正数r和c,分别表示想要重构成的新矩阵的行数和列数。

重构的矩阵需要由所有原来矩阵的元素以同样的顺序填充。

如果根据给出的参数进行重构操作是可能和合法的,就输出重构出的新矩阵,否则就输出原始矩阵。

例1:

输入:

nums =

[[1,2],

[3,4]]

r = 1, c = 4

输出:

[[1,2,3,4]]

解释:

原矩阵的顺序是[1,2,3,4]。新重构的是个1*4的矩阵,可以用上面的列表来一行行填充。

例2:

输入:

nums =

[[1,2],

[3,4]]

r = 2, c = 4

输出:

[[1,2],

[3,4]]

解释:

无法将22的矩阵重构为24的矩阵。因此输出原始矩阵。

注意:

  1. 给出的矩阵高宽在[1,100]范围内。
  2. 给出的r和c是正数。

思路:

也没什么特别的思路,就是遍历原二维数组,来按照数量建立新的二位数组,C++中用容器实现。唯一要注意的就是操作前的参数判断:是否为空数组、是否元素数一致、是否没变化之类的。

代码(C++):

class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
        if (nums.size() == 0) return nums;
        if ((r * c) != (nums.size() * nums[0].size())) return nums;
        if (r == nums.size()) return nums;
        
        vector<vector<int>> res;
        int oldC = 0;
        int oldR = 0;
        for (int i = 0; i < r; i++) {
            vector<int> newRow;
            for (int j = 0; j < c; j++) {
                if (oldC < nums[0].size()) {
                    newRow.push_back(nums[oldR][oldC]);
                    oldC++;
                } else {
                    oldR++;
                    oldC = 0;
                    newRow.push_back(nums[oldR][oldC]);
                    oldC++;
                }
            }
            res.push_back(newRow);
        }
        return res;
    }
};

他山之石:

别人的代码就简洁很多:

class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
        int m = nums.size(), n = nums[0].size(), o = m * n;
        if (r * c != o) return nums;
        vector<vector<int>> res(r, vector<int>(c, 0));
        for (int i = 0; i < o; i++) res[i / c][i % c] = nums[i / n][i % n];
        return res;
    }
};

回到目录


问题:

You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one island (i.e., one or more connected land cells). The island doesn't have "lakes" (water inside that isn't connected to the water around the island). One cell is a square with side length 1. The grid is rectangular, width and height don't exceed 100. Determine the perimeter of the island.

Example:

[[0,1,0,0],

[1,1,1,0],

[0,1,0,0],

[1,1,0,0]]

Answer: 16

Explanation: The perimeter is the 16 yellow stripes in the image below:

大意:

给你一个由二维整数网格组成的地图,其中1表示土地,0表示水。网格单元是水平/垂直接触的(不能斜对角)。网格完全被水包围,确定只会有一座岛屿(比如,一个或多个相连的土地单元)。岛屿没有湖(被岛屿包围的周围不与其他水相连的水)。一格单元是一个边长为1的方格。网格是矩形的,宽度和高度不会超过100。判断岛屿的边长。

例:

[[0,1,0,0],

[1,1,1,0],

[0,1,0,0],

[1,1,0,0]]

答案:16

解释:边长是下图中16个黄色条纹:

思路:

要注意对于边界上的土地单元,边界也算边长。我的想法是遍历每个格子,遇到土地时,判断前后左右是否是边界或水,是则给总边长加1。不过这样比较慢。

代码(C++):

class Solution {
public:
    int islandPerimeter(vector<vector<int>>& grid) {
        if (grid.size() == 0) return 0;
        
        int res = 0;
        int m = grid.size();
        int n = grid[0].size();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    int temp = 0;
                    if (i-1 < 0) temp++;
                    if (i+1 == m) temp++;
                    if (j-1 < 0) temp++;
                    if (j+1 == n) temp++;
                    if (i > 0 && grid[i-1][j] == 0) temp++;
                    if (i < m-1 && grid[i+1][j] == 0) temp++;
                    if (j > 0 && grid[i][j-1] == 0) temp++;
                    if (j < n-1 && grid[i][j+1] == 0) temp++;
                    
                    res = res + temp;
                }
            }
        }
        
        return res;
    }
};

他山之石:

可以不用判断那么多,首先记录有多少个土地格子,总边长乘以4,然后判断有无相邻的,有多少次相邻的,则要减去其两倍的边长数。

这样做的速度会快一倍。

class Solution {
public:
    int islandPerimeter(vector<vector<int>>& grid) {
        int count=0, repeat=0;
        for(int i=0;i<grid.size();i++)
        {
            for(int j=0; j<grid[i].size();j++)
                {
                    if(grid[i][j]==1)
                    {
                        count ++;
                        if(i!=0 && grid[i-1][j] == 1) repeat++;
                        if(j!=0 && grid[i][j-1] == 1) repeat++;
                    }
                }
        }
        return 4*count-repeat*2;
    }
};

回到目录


问题:

You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subset of nums2. Find all the next greater numbers for nums1's elements in the corresponding places of nums2.

The Next Greater Number of a number x in nums1 is the first greater number to its right in nums2. If it does not exist, output -1 for this number.

Example 1:

Input: nums1 = [4,1,2], nums2 = [1,3,4,2].

Output: [-1,3,-1]

Explanation:

For number 4 in the first array, you cannot find the next greater number for it in the second array, so output -1.

For number 1 in the first array, the next greater number for it in the second array is 3.

For number 2 in the first array, there is no next greater number for it in the second array, so output -1.

Example 2:

Input: nums1 = [2,4], nums2 = [1,2,3,4].

Output: [3,-1]

Explanation:

For number 2 in the first array, the next greater number for it in the second array is 3.

For number 4 in the first array, there is no next greater number for it in the second array, so output -1.

Note:

  1. All elements in nums1 and nums2 are unique.
  2. The length of both nums1 and nums2 would not exceed 1000.

大意:

给你两个数组(非复制)nums1和nums2,nums1的元素是nums2的子集。找到nums1中所有元素在nums2对应位置的下一个更大数。

nums1中x的下一个更大数是其在nums2中右边遇到的第一个更大的数。如果不存在,则为这个数输出-1。

例1:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].

输出: [-1,3,-1]

解释:

对于第一个数组的数字4,你无法在第二个数组找到下一个更大数,所以输出-1。

对于第一个数组的数字1,第二个数组中下一个更大数是3。

对于第一个数组的数字2,无法在第二个数组找到下一个更大数,所以输出-1。

例2:

输入:nums1 = [2,4], nums2 = [1,2,3,4].

输出: [3,-1]

解释:

对于第一个数组的数字2,第二个数组中下一个更大数是3。

对于第一个数组的数字4,无法在第二个数组找到下一个更大数,所以输出-1。

注意:

  1. nums1和nums2中所有元素都是唯一的。
  2. nums1和nums2的长度都不超过1000。

他山之石:

最简单呆板的做法就是循环遍历,但是时间复杂度太高,肯定有更好的方法。

数组1虽然是数组2的子集,但顺序并不一样,所以两个数组都不能修改。

这里用一个stack来尝试得到数组二中每个数字后面的下一个更大数,用一个map来记录。遍历数组二,每次取栈顶的数字(也就是最近的上一个数字)判断当前数是否大于它,大于则说明此数就是栈顶数字的下一个更大数,记录到map中去,并继续循环取栈顶数比较(这很重要!),如果不大,那么放入stack,等待读取下一个数时做比较。这样遍历一次数组2就可以得到一个记录了有下一个更大数的map了,没记录在map中的说明就是没找到下一个更大数的。

然后遍历数组1,因为是数组2的子集,所以直接看map中有没有记录,有则取出来,没有则输出位-1,这样就得到了,时间复杂度大大降低。

代码(C++):

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& findNums, vector<int>& nums) {
        stack<int> s;
        unordered_map<int, int> m;
        for (int n : nums) {
            while (s.size() > 0 && s.top() < n) {
                m[s.top()] = n;
                s.pop();
            }
            s.push(n);
        }
        
        vector<int> res;
        for (int n : findNums) {
            if (m.count(n) > 0) res.push_back(m[n]);
            else res.push_back(-1);
        }
        return res;
    }
};

回到目录


问题:

Given a non-empty binary tree, return the average value of the nodes on each level in the form of an array.

Example 1:

Input:

Output: [3, 14.5, 11]

Explanation:

The average value of nodes on level 0 is 3, on level 1 is 14.5, and on level 2 is 11. Hence return [3, 14.5, 11].

Note:

  1. The range of node's value is in the range of 32-bit signed integer.

大意:

给出一个非空的二叉树,以数组的形式返回每一层的节点平均值。

例1:

输入:

输出: [3, 14.5, 11]

解释:

0层的平均值是3,1层的平均值是14.5,2层的平均值是11。所以返回 [3, 14.5, 11]。

注意:

  1. 节点值的范围为32比特的有符号整型。

思路:

要计算每一层的平均值,肯定用BFS的遍历方式了,使用一个队列来遍历二叉树,同时用一个数来记录每层的节点数,遍历队列的过程中不断把左右子节点加入到队列后,同时增加记录下一层数量的变量,且累加每个节点的值。遍历完这一层应有的节点数就可以计算该层的平均值了,都添加到一个数组中去即可。

需要注意的是节点值范围比较大,需要用long型变量来记录和。

代码(C++):

/**
 * 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:
    vector<double> averageOfLevels(TreeNode* root) {
        vector<double> res;
        if (root == NULL) return res;
        queue<TreeNode> q;
        q.push(*root);
        int levelNum = 1;
        while (!q.empty()) {
            int temp = levelNum;
            levelNum = 0;
            long sum = 0;
            for (int i = 0; i < temp; i++) {
                TreeNode node = q.front();
                q.pop();
                sum = sum + node.val;
                if (node.left != NULL) {
                    q.push(*node.left);
                    levelNum++;
                }
                if (node.right != NULL) {
                    q.push(*node.right);
                    levelNum++;
                }
            }
            res.push_back((double)sum / (double)temp);
        }
        return res;
    }
};

回到目录


问题:

Given two lists Aand B, and B is an anagram of A. B is an anagram of A means B is made by randomizing the order of the elements in A.

We want to find an index mapping P, from A to B. A mapping P[i] = j means the ith element in A appears in B at index j.

These lists A and B may contain duplicates. If there are multiple answers, output any of them.

For example, given

A = [12, 28, 46, 32, 50]

B = [50, 12, 32, 46, 28]

We should return

[1, 4, 3, 2, 0]

as P[0] = 1 because the 0th element of A appears at B[1], and P[1] = 4 because the 1st element of A appears at B[4], and so on.

Note:

  1. A, B have equal lengths in range [1, 100].
  2. A[i], B[i] are integers in range [0, 10^5].

大意:

给出两个列表A和B,B是A的异构体,所谓异构体是指B是由A中元素随机顺序组成。

我们想要找到一个从A到B的序号映射P,映射P[i] = j表示A中的第i个元素在B中是序号j。

这些列表A和B可能包含重复的,如果有多个答案,输出任何一个。

比如,给出

A = [12, 28, 46, 32, 50]

B = [50, 12, 32, 46, 28]

我们应该返回

[1, 4, 3, 2, 0]

P[0] = 1,因为A中第0个元素在B[1]出现,P[1] = 4,因为A中第1个元素在B[4]出现,等等。

注意:

  1. A、B有着相等的长度,范围在[1,100]。
  2. A[i],B[i]都是范围在[0,10^5]的整数。

思路:

题目说了出现重复的随便取一个位置即可,测试了一下,即使两个元素都取一个位置也行,那就简单了,因为要确定序号,所以顺序也不能变,直接调用find函数,即可找到元素在B中的位置。

代码(C++):

class Solution {
public:
    vector<int> anagramMappings(vector<int>& A, vector<int>& B) {
        vector<int> res;
        auto iter = A.begin();
        while (iter != A.end()) {
            auto findB = find(B.begin(), B.end(), *iter);
            res.push_back(findB-B.begin());
            iter++;
        }
        return res;
    }
};

他山之石:

可以用map存储B中每个元素的位置,然后遍历A,利用map找到其在B中的位置即可,速度比上面的方法要快。

class Solution {
public:
    vector<int> anagramMappings(vector<int>& A, vector<int>& B) {
        unordered_map<int, int> m;    //<values of b, index in b>
        for(int i=0; i<B.size(); i++)
            m[B[i]]=i;    
        
        vector<int> ans(A.size());
        for(int i=0; i<A.size(); i++) {
            auto loc = m.find(A[i]);
            ans[i]=loc->second;
        }
        
        return ans;
    }
};

回到目录


问题:

Given a positive integer, check whether it has alternating bits: namely, if two adjacent bits will always have different values.

Example 1:

Input: 5

Output: True

Explanation:

The binary representation of 5 is: 101

Example 2:

Input: 7

Output: False

Explanation:

The binary representation of 7 is: 111.

Example 3:

Input: 11

Output: False

Explanation:

The binary representation of 11 is: 1011.

Example 4:

Input: 10

Output: True

Explanation:

The binary representation of 10 is: 1010.

大意:

给出一个正整数,检查其是否有交替的比特位:也就是说,如果相邻的两个比特位始终有不同的值。

例1:

输入:5

输出:True

解释:

5的二进制表示是:101

例2:

输入:7

输出:False

解释:

5的二进制表示是:111

例3:

输入:11

输出:False

解释:

5的二进制表示是:1011

例4:

输入:10

输出:True

解释:

5的二进制表示是:1010

思路:

最简单的就是循环右移并用模2来获得最低位的值,并依次进行比较看是否不同。

代码(C++):

class Solution {
public:
    bool hasAlternatingBits(int n) {
        int temp = n % 2;
        while (n > 0) {
            n = n >> 1;
            if (n % 2 == temp) return false;
            temp = n % 2;
        }
        return true;
    }
};

他山之石:

交替的比特位其实是个很有特点的排列,如果右移一次,得到的数可以刚好和原本的数互补,比如101010,右移得到10101:

101010 010101

相加就会得到:

11111

我们可以利用这个特点,如果是交替的比特位,右移一位后相加可以得到一个全是1的数,再加1会的得到一个最高位为1,其余位为0的数,与全是1的数相与将为0,但如果不是交替的比特位,就没有这个特性,因此可以这样判断,速度更快。

class Solution {
public:
    bool hasAlternatingBits(int n) {
        return (((long)n + (n>>1) + 1) & ((long)n + (n>>1))) == 0;
    }
};

回到目录


问题:

Given two integers L and R, find the count of numbers in the range [L, R] (inclusive) having a prime number of set bits in their binary representation.

(Recall that the number of set bits an integer has is the number of 1s present when written in binary. For example, 21 written in binary is 10101 which has 3 set bits. Also, 1 is not a prime.)

Example 1:

Input: L = 6, R = 10

Output: 4

Explanation:

6 -> 110 (2 set bits, 2 is prime)

7 -> 111 (3 set bits, 3 is prime)

9 -> 1001 (2 set bits , 2 is prime)

10->1010 (2 set bits , 2 is prime)

Example 2:

Input: L = 10, R = 15

Output: 5

Explanation:

10 -> 1010 (2 set bits, 2 is prime)

11 -> 1011 (3 set bits, 3 is prime)

12 -> 1100 (2 set bits, 2 is prime)

13 -> 1101 (3 set bits, 3 is prime)

14 -> 1110 (3 set bits, 3 is prime)

15 -> 1111 (4 set bits, 4 is not prime)

Note:

  1. L, R will be integers L <= R in the range [1, 10^6].
  2. R - L will be at most 10000.

大意:

给出两个整数L和R,找出[L,R](包含)范围中的数字以二进制表示时所拥有的比特位为1的数量为质数的总个数。

(比如,21写成二进制为10101,有三个比特位为1。此外,1不是质数。)

例1:

输入:L = 6, R = 10

输出:4

解释:

6 -> 110 (2 个1, 2 是质数)

7 -> 111 (3 个1, 3 是质数)

9 -> 1001 (2 个1 , 2 是质数)

10->1010 (2 个1 , 2 是质数)

例2:

输入:L = 10, R = 15

输出:5

10 -> 1010 (2 个1, 2 是质数)

11 -> 1011 (3 个1, 3 是质数)

12 -> 1100 (2 个1, 2 是质数)

13 -> 1101 (3 个1, 3 是质数)

14 -> 1110 (3 个1, 3 是质数)

15 -> 1111 (4 个1, 4 不是质数)

注意:

  1. L、R会是[1,10^6]范围内的整数。
  2. R - L最多为10000。

思路:

没有特别简单的方法,只能对于范围内每个数字去数其二进制表示形式下有几个1,这一点可以通过右移操作来一位位判断。然后对于1的个数,判断其是否是质数,因为R最多为10^6,所以最大的数字转换成二进制是20位,也就是最多有二十个1,那么只需要知道1~20有哪些质数就可以了,并不多,只有2、3、5、7、11、13、17、19,可以发现剩下的数字除了1以外都能被2或者3整除,所以可以直接判断了。

代码(C++):

class Solution {
public:
    int countPrimeSetBits(int L, int R) {
        int res = 0;
        for (int i = L; i <= R; i++) {
            int num = 0;
            int temp = i;
            while (temp > 0) {
                if (temp & 1 == 1) num++;
                temp = temp >> 1;
            }
            
            if (num == 2 || num == 3) res++;
            else if (num != 1 && num % 2 !=0 && num % 3 != 0) res++;
        }
        return res;
    }
};

当然,判断质数的时候可以用set来判断,可能会更快一些。

class Solution {
public:
    int countPrimeSetBits(int L, int R) {
        set<int> primes = { 2, 3, 5, 7, 11, 13, 17, 19 };
        int res = 0;
        for (int i = L; i <= R; i++) {
            int num = 0;
            int temp = i;
            while (temp > 0) {
                if (temp & 1 == 1) num++;
                temp = temp >> 1;
            }
            
            res += primes.count(num);
        }
        return res;
    }
};

回到目录


问题(Easy):

Given a binary array, find the maximum number of consecutive 1s in this array.

Example 1:

Input: [1,1,0,1,1,1]

Output: 3

Explanation: The first two digits or the last three digits are consecutive 1s.

The maximum number of consecutive 1s is 3.

Note:

  1. The input array will only contain 0 and 1.
  2. The length of input array is a positive integer and will not exceed 10,000

大意:

给出一个二进制数组,在数组中找到连续的1最大的长度。

例1:

输入:[1,1,0,1,1,1]

输出:3

解释:开头两个数字和最后三个数字是连续的1。

最大的连续的1是3个。

注意:

  1. 输入的数组只包含0和1。
  2. 输入数组的长度是个正整数且不超过10000。

思路:

无非就是遍历数组,检查连续的1,用一个临时变量记录每次连续的1的个数,连续结束时判断是否比最大的连续个数要大。

代码的写法可以有很多种,也会随着写法不同带来一些效率差异,但时间复杂度是一样的。

代码(C++):

class Solution {
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
        int res = 0;
        int temp = 0;
        bool flag = true;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == 1) {
                if (flag) temp++;
                else {
                    temp = 1;
                    flag = true;
                }
                if (temp > res) res = temp;
            } else 
                if (flag) flag = false;
        }
        return res;
    }
};

回到目录


问题(Easy):

Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water.

Find the maximum area of an island in the given 2D array. (If there is no island, the maximum area is 0.)

Example 1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],

[0,0,0,0,0,0,0,1,1,1,0,0,0],

[0,1,1,0,1,0,0,0,0,0,0,0,0],

[0,1,0,0,1,1,0,0,1,0,1,0,0],

[0,1,0,0,1,1,0,0,1,1,1,0,0],

[0,0,0,0,0,0,0,0,0,0,1,0,0],

[0,0,0,0,0,0,0,1,1,1,0,0,0],

[0,0,0,0,0,0,0,1,1,0,0,0,0]]

Given the above grid, return 6. Note the answer is not 11, because the island must be connected 4-directionally.

Example 2:

[[0,0,0,0,0,0,0,0]]

Given the above grid, return 0.

Note: The length of each dimension in the given grid does not exceed 50.

大意:

给出一个非空的二维数组grid,由0和1组成,一个岛是指由1通过四个方向(水平或垂直)组成的一块区域(代表陆地)。你可以假设grid的四边都是水。

找到给出的二维数组中最大区域的岛。(如果没有岛,最大区域就是0)。

例1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],

[0,0,0,0,0,0,0,1,1,1,0,0,0],

[0,1,1,0,1,0,0,0,0,0,0,0,0],

[0,1,0,0,1,1,0,0,1,0,1,0,0],

[0,1,0,0,1,1,0,0,1,1,1,0,0],

[0,0,0,0,0,0,0,0,0,0,1,0,0],

[0,0,0,0,0,0,0,1,1,1,0,0,0],

[0,0,0,0,0,0,0,1,1,0,0,0,0]]

给出上面的grid,返回6。注意答案不是11,因为岛必须由四个方向连接。

例2:

[[0,0,0,0,0,0,0,0]]

给出上面的grid,返回0。

注意:给出的grid每个方向的长度不会超过50。

思路:

我们遍历二维数组的时候,遇到1了,肯定是要检查上下左右是否依然是1(同时注意不要超出边界),如果检查出某一边是1,则还要进一步继续检查它的上下左右是否是1,这说明我们要通过递归来做,遍历时每遇到一个1,就放到递归中去检测并计算岛屿面积。

此外,为了避免循环计算重复的区域,我们要改变已经计算过的岛屿的位置的值,可以从1改成2。

这种递归方式其实就是一种DFS,遇到一个1,则找遍其四周及四周的四周等等,来计算一个岛屿面积,同时改变找过的1的值,避免重复计算。

代码(C++):

class Solution {
public:
    int findLand(vector<vector<int>>& grid, int i, int j) {
        grid[i][j] = 2;
        int sum = 1;
        if (i > 0 && grid[i-1][j] == 1) sum = sum + findLand(grid, i-1, j);
        if (i < grid.size()-1 && grid[i+1][j] == 1) sum = sum + findLand(grid, i+1, j);
        if (j > 0 && grid[i][j-1] == 1) sum = sum + findLand(grid, i, j-1);
        if (j < grid[0].size()-1 && grid[i][j+1] == 1) sum = sum + findLand(grid, i, j+1);
        return sum;
    }
    
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        if (grid.size() == 0) return 0;
        int res = 0;
        for (int i = 0; i < grid.size(); i++) {
            for (int j = 0; j < grid[0].size(); j++) {
                if (grid[i][j] == 1) {
                    int temp = findLand(grid, i, j);
                    res = max(temp, res);
                }
            }
        }
        return res;
    }
};

回到目录


问题:

Given an 2D board, count how many different battleships are in it. The battleships are represented with 'X's, empty slots are represented with '.'s. You may assume the following rules:

  • You receive a valid board, made of only battleships or empty slots.
  • Battleships can only be placed horizontally or vertically. In other words, they can only be made of the shape 1xN (1 row, N columns) or Nx1 (N rows, 1 column), where N can be of any size.
  • At least one horizontal or vertical cell separates between two battleships - there are no adjacent battleships.

Example:

In the above board there are 2 battleships.

Invalid Example:

This is an invalid board that you will not receive - as battleships will always have a cell separating between them.

Follow up: Could you do it in one-pass, using only O(1) extra memory and without modifying the value of the board?

大意:

给出一个2D面板,计算其中有多少不同的战舰。战舰由‘X’表示,空地由‘.’表示。你可以假设满足下面的规则:

  • 你会接受到一个有效的面板,只由战舰和空地组成。
  • 战舰只能是水平的或者垂直的。也就是说,只能是 1xN(一行N列)或者 Nx1(N行一列)的形状,N可以是任何尺寸。
  • 两个战舰之间至少有一个空地 - 没有相连的战舰。

例子:

上面的面板上有两艘战舰。

无效的例子:

这是一个无效的面板,你不会接受到 - 因为两艘战舰一定会有空白的点。

进阶: 你能不能使用O(1)的内存,并且不修改面板的值来完成?

思路:

这道题的情景很像我们玩的战船游戏,所以做起来也很有意思。

我们最好从左上角开始,遍历每个点,如果有X,就看它往右和往下有没有相邻的X,有的话也算做一艘战舰。已经检查过的点我们就不检查了,所以用一个二维数组来记录检查过的点。

代码(Java):

public class Solution {
    public int countBattleships(char[][] board) {
        int result = 0;
        // 用来记录有没有判断过
        int[][] tempBoard = new int[board.length][board[0].length];
        for (int i = 0; i < tempBoard.length; i++) {
            for (int j = 0; j < tempBoard[0].length; j++) tempBoard[i][j] = 0;
        }
        
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (tempBoard[i][j] == 0) {
                    if (board[i][j] - 'X' == 0) {
                        System.out.println(i + " " + j);
                        if (j+1 < board[0].length && board[i][j+1] - 'X' == 0) {// 竖向战舰
                            for (int k = j+1; k < board[0].length; k++) {
                                if (board[i][k] - 'X' == 0) tempBoard[i][k] = 1;
                                else break;
                            }
                        } else if (i+1 < board.length && board[i+1][j] - 'X' == 0) {// 横向战舰
                            for (int k = i+1; k < board.length; k++) {
                                if (board[k][j] - 'X' == 0) tempBoard[k][j] = 1;
                                else break;
                            }
                        }
                        
                        tempBoard[i][j] = 1;
                        result ++;
                    }
                }
            }
        }
        
        return result;
    }
}

他山之石:

    public int countBattleships(char[][] board) {
        int m = board.length;
        if (m==0) return 0;
        int n = board[0].length;
        
        int count=0;
        
        for (int i=0; i<m; i++) {
            for (int j=0; j<n; j++) {
                if (board[i][j] == '.') continue;
                if (i > 0 && board[i-1][j] == 'X') continue;
                if (j > 0 && board[i][j-1] == 'X') continue;
                count++;
            }
        }
        
        return count;
    }

上面我的做法其实用了O(n)的内存,而且要进行多次循环,很耗时,这个就简单多了,每次遇到一个坐标,如果它既不是空地,他的上面和左边也没有X,那就说明这是一个新战舰,只记录这种新战舰的个数,很节省空间和时间。

回到目录


问题:

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1's in their binary representation and return them as an array.

Example: For num = 5 you should return [0,1,1,2,1,2].

Follow up:

  • It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
  • Space complexity should be O(n).
  • Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.

大意:

给出一个非负整数num。对于 0 ≤ i ≤ num 范围的每个数计算它们二进制表示的数中的1的个数,并返回它们组成的数组。

例子: 对 num = 5 你应该返回 [0,1,1,2,1,2]。

进阶:

  • 很容易找到时间复杂度为 O(n*sizeof(integer))的解决方案。但你能不能在线性时间复杂度O(n)中解决呢?
  • 康健复杂度需要是O(n)。 你能不能像一个boss一样做?不要使用像c++中 __builtin_popcount 一样的内置的函数去做。

思路:

把0~7的二进制表示法的数字列出来,数其中的1的个数,找到一个规律,0对应的数是0,1、2对应的是1个1。往上走只用计算不断除以2一直除到1后,存在余数为1的次数,加上最后的1,就是该数二进制表示法中1的个数。

注意初始化结果数组的时候容量为 num+1,不是 num。

我的做法时间复杂度应该是O(nlogn)。

初始化int型数组后,数组所有元素默认为0,所以对0的判断处理可以略去。

代码(Java):

public class Solution {
    public int[] countBits(int num) {
        int[] result = new int[num+1];
        for (int i = 0; i <= num; i++) {
            if (i == 0) result[i] = 0;
            else if (i <= 2) result[i] = 1;
            else {
                int numberOfOne = 1;
                int number = i;
                while (number > 1) {
                    numberOfOne += number % 2;
                    number = number / 2;
                }
                result[i] = numberOfOne;
            }
        }
        return result;

    }
}

他山之石:

public int[] countBits(int num) {
    int[] f = new int[num + 1];
    for (int i=1; i<=num; i++) f[i] = f[i >> 1] + (i & 1);
    return f;
}

这个做法把上面的**简化了很多,i&1其实就是看最后一位有没有1,也就是取余为1。然后加上 f[i >> 1],这个其实就是当前数字除以2后对应的数字的1的个数,所以可以看出我的做法做了很多无用功,因为没有利用到已经得出的结果,而这个做法的时间复杂度就是O(n)了。

回到目录


问题:

Suppose you have a random list of people standing in a queue. Each person is described by a pair of integers (h, k), where h is the height of the person and k is the number of people in front of this person who have a height greater than or equal to h. Write an algorithm to reconstruct the queue.

Note: The number of people is less than 1,100.

Example

Input: [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

Output: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

大意:

假设你有一个随机顺序的人站在队列中。每个人都被一对整数 (h, k) 描述,h 是人的高度,k 是站在他前面的人中高度大于等于他的数量。写一个算法来重构队列。

注意: 人的数量少于1100。

例子:

输入: [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

输出: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

思路:

这个需求中其实主要要抓到最大的规律。

首先我们将人全部从高到低排个序,如果一样高,就看谁的 k 大谁就拍前面。这时我们得到了一堆从高到低的,等高中 k 大在前的人。

现在,遍历这个排序后的数组,按照每个人的 k 的值将其一个个插入到对应的位置。相同位置的,后插入的一定小于先插入的,所以在前面,而越后插入的人,已经插过的所有人都比他高,它插在任何位置,前面的人都比他高,高的个数就正好是他的 k 值。即使后面又在他前面插了人,也比他矮。

PS:List 的 add 方法如果带有两个参数,第一个整型参数是指插入的位置。

他山之石:

public class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people,new Comparator<int[]>(){
           public int compare(int[] p1, int[] p2){
               return p1[0]!=p2[0]?Integer.compare(p2[0],p1[0]): Integer.compare(p1[1],p2[1]);
           }
        });
        List<int[]> list = new LinkedList();
        for (int[] ppl: people) list.add(ppl[1], ppl);
        return list.toArray(new int[people.length][] );
    }
}

回到目录


问题:

A sequence of number is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.

For example, these are arithmetic sequence:

1, 3, 5, 7, 9

7, 7, 7, 7

3, -1, -5, -9

The following sequence is not arithmetic.

1, 1, 2, 5, 7

A zero-indexed array A consisting of N numbers is given. A slice of that array is any pair of integers (P, Q) such that 0 <= P < Q < N.

A slice (P, Q) of array A is called arithmetic if the sequence: A[P], A[p + 1], ..., A[Q - 1], A[Q] is arithmetic. In particular, this means that P + 1 < Q.

The function should return the number of arithmetic slices in the array A.

Example:

A = [1, 2, 3, 4]

return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.

大意:

如果一序列的数字至少由三个数字组成且每两个相邻的数字间的差值都一样,就称该序列为 arithmetic 。

比如,下面这些是 arithmetic 序列:

1, 3, 5, 7, 9

7, 7, 7, 7

3, -1, -5, -9

而下面的序列不是 arithmetic 序列:

1, 1, 2, 5, 7

一个零索引的数组A由N个数字组成。其中的一截数字 (P,Q), 0 <= P < Q < N。

如果序列 A[P], A[p + 1], ..., A[Q - 1], A[Q] 是arithmetic,就称数组的一截数字(P,Q)是 arithmetic。尤其是,这意味着 P + 1 < Q。

函数需要返回数组A中 arithmetic 截的数量。

例子:

A = [1, 2, 3, 4] 返回 3,因为A中有三截arithmetic : [1, 2, 3], [2, 3, 4] 和 [1, 2, 3, 4] (它自己)。

思路:

题目的意思就是找数组中有多少节三个数以上组成的等差数列。我的做法一个个数开始往后遍历,对每个数都看能找几节,从3个数一截开始判断,如果是就算一节,然后长度加一再判断,知道数组末尾。对每个数都这样判断一次。至于怎么判断等差数列就很简单了。

代码(Java):

public class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        int result = 0;
        for (int i = 0; i < A.length - 2; i++) {
            int gap = A[i+1] - A[i];
            for (int j = i+2; j < A.length; j++) {
                if (A[j] - A[j-1] == gap) result ++;
                else break;
            }
        }
        return result;
    }
}

他山之石:

public int numberOfArithmeticSlices(int[] A) {
    int curr = 0, sum = 0;
    for (int i=2; i<A.length; i++)
        if (A[i]-A[i-1] == A[i-1]-A[i-2]) {
            curr += 1;
            sum += curr;
        } else {
            curr = 0;
        }
    return sum;
}

这个做法只需要遍历一次,他发现了一个规律,就是每找到更长的一节,其实就代表其内还包含了很多节,而这个数量也是递增的。

回到目录


问题:

Given a non-empty integer array, find the minimum number of moves required to make all array elements equal, where a move is incrementing a selected element by 1 or decrementing a selected element by 1.

You may assume the array's length is at most 10,000.

Example:

Input:

[1,2,3]

Output:

2

Explanation:

Only two moves are needed (remember each move increments or decrements one element):

[1,2,3] => [2,2,3] => [2,2,2]

大意:

给出一个非空整型数组,找到需要移动的最小值来让数组的所有元素都相等,一次移动是指将指定元素加一或者减一。

你可以假设数组的长度不超过10000。

例子:

输入:

[1,2,3]

输出:

2

解释:

只需要两次移动(记住每次移动是指增减一个元素):

[1,2,3] => [2,2,3] => [2,2,2]

思路:

题目的描述有一点误导性,主要是用了“移动”这个词,而且给出的例子也不够明确,一开始我误以为是要将元素进行位移,导致想的很复杂,后来才发现是对元素进行加减。

只是加减就很简单了,我们要通过最小的加减数来使所有的元素都相同,最快的方式是往中间靠拢,这就需要先给数组排序,然后取其中间的数,由于每次“移动”都只能加一或者减一,所以“移动”的次数其实就是两数之间的差值。这样遍历一次都进行一次减法就行了,当然要记得取绝对值。

代码(Java):

public class Solution {
    public int minMoves2(int[] nums) {
        Arrays.sort(nums);
        int middle = nums[nums.length/2];
        int result = 0;
        for (int i = 0; i < nums.length; i++) {
            result += Math.abs(middle - nums[i]);
        }
        return result;
    }
}

他山之石:

public class Solution {
    public int minMoves2(int[] nums) {
        Arrays.sort(nums);
        int i = 0, j = nums.length-1;
        int count = 0;
        while(i < j){
            count += nums[j]-nums[i];
            i++;
            j--;
        }
        return count;
    }
}

同样的思路,这种做法理想情况下会快一半。

回到目录


问题:

Given a string, sort it in decreasing order based on the frequency of characters.

Example 1:

Input:

"tree"

Output:

"eert"

Explanation:

'e' appears twice while 'r' and 't' both appear once.

So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.

Example 2:

Input:

"cccaaa"

Output:

"cccaaa"

Explanation:

Both 'c' and 'a' appear three times, so "aaaccc" is also a valid answer.

Note that "cacaca" is incorrect, as the same characters must be together.

Example 3:

Input:

"Aabb"

Output:

"bbAa"

Explanation:

"bbaA" is also a valid answer, but "Aabb" is incorrect.

Note that 'A' and 'a' are treated as two different characters.

大意:

给出一个字符串,基于其中字符出现的次数按照降序排列。

例1:

输入:

"tree"

输出:

"eert"

解释:

‘e’出现了两次,‘r’和‘t’都只出现了一次。

所以‘e’要在‘r’和‘t’的前面。因此“eetr”也是对的。

例2:

输入:

"cccaaa"

输出:

"cccaaa"

解释:

‘c’和‘a’都出现了三次,所以“aaaccc”也是对的。

注意“cacaca”是错的,因为同样的字符必须在一起。

例3:

输入:

"Aabb"

输出:

"bbAa"

解释:

“bbaA”也是对的,但“Aabb”不对。

注意‘A’和‘a’被视为两个不同的字符。

思路:

题目的要求是将字符按照出现次数排序,注意是区分大小写的,而且并没有说只有字母。

其实整个过程可以分为几步:

  1. 遍历收集不同字符出现的次数;
  2. 按照次数排序
  3. 组成结果数组,每个字符要出现对应的次数。

我用二维数组来记录字符和出现的次数,然后用冒泡法排序,最后组成数组。

这个做法能实现,但是时间复杂度太高了,在超长输入的测试用例中超时了。

代码(Java):

public class Solution {
    public String frequencySort(String s) {
        char[] arr = s.toCharArray();
        String[][] record = new String[arr.length][2];
        int num = 0;
        // 将字符串中的不同字符及其数量记录到二维数组中
        for (int i = 0; i < arr.length; i++) {
            int j = 0;
            boolean hasFind = false;
            for (; j < num; j++) {
                if (arr[i] - record[j][0].charAt(0) == 0) {
                    hasFind = true;
                    int temp = Integer.parseInt(record[j][1]) + 1;
                    record[j][1] = Integer.toString(temp);
                    break;
                }
            }
            if (!hasFind) {
                record[j][0] = String.valueOf(arr[i]);
                record[j][1] = "1";
                num ++;
            }
        }
        
        // 对二维数组按第二列排序
        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num-1; j++) {
                String[] temp = new String[2];
                if (Integer.parseInt(record[j][1]) - Integer.parseInt(record[j+1][1]) > 0) {
                    temp = record[j];
                    record[j] = record[j+1];
                    record[j+1] = temp;
                }
            }
        }
        // 结果
        System.out.println(num);
        String result = "";
        for (int i = num-1; i >= 0; i--) {
            System.out.println(record[i][1]);
            System.out.println(record[i][0]);
            for (int j = 0; j < Integer.parseInt(record[i][1]); j++) {
                result = result + record[i][0];
            }
        }
        return result;
    }
}

改进:

其实想了想ASCII码表总共就126个字符,可以像对待纯字母一样用一个数组来记录次数,同时用一个字符数组来记录对应位置的字符,在排序时跟着一起变就行了。

但是依然会超时,看来主要还是排序方式太慢了。

改进代码(Java):

public class Solution {
    public String frequencySort(String s) {
        char[] arr = s.toCharArray();
        int[] map = new int[126];
        char[] c = new char[126];
        // 将字符串中的不同字符及其数量记录到数组中
        for (char ch : s.toCharArray()) {
            map[ch]++;
        }
        
        // 对应字符
        for (int i = 0; i < 126; i++) {
            c[i] = (char)i;
        }
        
        // 对次数排序
        for (int i = 0; i < 126; i++) {
            for (int j = 0; j < 125; j++) {
                if (map[j] - map[j+1] > 0) {
                    // 次数移动
                    int tempNum = map[j];
                    map[j] = map[j+1];
                    map[j+1] = tempNum;
                    
                    // 对应字符移动
                    char tempChar = c[j];
                    c[j] = c[j+1];
                    c[j+1] = tempChar;
                }
            }
        }
        // 结果
        String result = "";
        for (int i = 125; i >= 0; i--) {
            if (map[i] > 0) {
                for (int j = 0; j < map[i]; j++) {
                    result = result + c[i];
                }
            }
        }
        return result;
    }
}

他山之石:

public class Solution {
    public String frequencySort(String s) {
        if (s == null) {
            return null;
        }
        Map<Character, Integer> map = new HashMap();
        char[] charArray = s.toCharArray();
        int max = 0;
        for (Character c : charArray) {
            if (!map.containsKey(c)) {
                map.put(c, 0);
            }
            map.put(c, map.get(c) + 1);
            max = Math.max(max, map.get(c));
        }
    
        List<Character>[] array = buildArray(map, max);
    
        return buildString(array);
    }
    
    private List<Character>[] buildArray(Map<Character, Integer> map, int maxCount) {
        List<Character>[] array = new List[maxCount + 1];
        for (Character c : map.keySet()) {
            int count = map.get(c);
            if (array[count] == null) {
                array[count] = new ArrayList();
            }
            array[count].add(c);
        }
        return array;
    }
    
    private String buildString(List<Character>[] array) {
        StringBuilder sb = new StringBuilder();
        for (int i = array.length - 1; i > 0; i--) {
            List<Character> list = array[i];
            if (list != null) {
                for (Character c : list) {
                    for (int j = 0; j < i; j++) {
                        sb.append(c);
                    }
                }
            }
        }
        return sb.toString();
    }
}

这个做法用map来记录字符出现的次数,比我的方法要快一些。然后创建数组,用序号来表示其出现的次数,省去了排序的过程,最后反过来得出结果就可以了。速度回快一些,但是属于空间换时间,在字符数量很大时会浪费很多空间。

回到目录


问题:

Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.

For example:

Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].

Note:

  1. The order of the result is not important. So in the above example, [5, 3] is also correct.
  2. Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?

大意:

给出一个数字数组 nums,其中只有两个元素只出现过一次,其余的都出现了两次。找到只出现了一次的两个元素。

例子:

给出 nums = [1, 2, 1, 3, 2, 5], 返回 [3, 5].

注意:

  1. 结果的顺序不重要,所以上面的例子中 [5, 3] 也是对的。
  2. 你的算法应该为线性的时间复杂度。你能不能只用恒定的空间复杂度?

思路:

最简单的方法就是排序后,依次检查相邻两个数是否相等,当然遇到相等的就要跳一个数字再进行检查,遇到不相等的就记录下来是结果,注意如果单个的元素排序后在最末尾,要单独处理。

这个做法排序是需要时间和空间的,并不完全符合题目的要求。

代码(Java):

public class Solution {
    public int[] singleNumber(int[] nums) {
        Arrays.sort(nums);
        
        int[] result = new int[2];
        int index = 0;
        
        int i = 0;
        for (; i < nums.length-1; i++) {
            if (nums[i] != nums[i+1]) {
                result[index] = nums[i];
                index ++;
            }
            else i++;
        }
        if (i < nums.length) result[index] = nums[i];
        
        return result;
    }
}

他山之石:

public class Solution {
    public int[] singleNumber(int[] nums) {
        // Pass 1 : 
        // Get the XOR of the two numbers we need to find
        int diff = 0;
        for (int num : nums) {
            diff ^= num;
        }
        // Get its last set bit
        diff &= -diff;
        
        // Pass 2 :
        int[] rets = {0, 0}; // this array stores the two numbers we will return
        for (int num : nums)
        {
            if ((num & diff) == 0) // the bit is not set
            {
                rets[0] ^= num;
            }
            else // the bit is set
            {
                rets[1] ^= num;
            }
        }
        return rets;
    }
}

好的,这是一个完全符合题目要求的做法,用到了按位异或和与的操作,是什么意思呢?

首先我们要知道异或的操作是指两个数同一位上相等时结果为0,不等时结果为1。如果是两个相同的数字异或,结果自然为0,而0与一个数异或,结果也是那个数,按照我们数组的特性,遍历异或后的结果将会是两个唯一的元素的异或结果。

得到两个数的异或结果后,我们要知道,这个结果中为0的位上说明两个数字相同,为1的位上说明两个数字不同,对于这一位,将数组中所有数字和其相与,必然会得到为0和为1两个结果阵营,而我们要的两个数就在两个阵营之中,那怎么分别在两个阵营中找出两个数呢?还是上面用过的手法,遍历异或。因为相同的两个数一定会进入同一个阵营,异或后又变成0了,最后就会凸显出要找的两个数了。

上面我们说要将第一次异或后的结果中找出某一位为1的值,怎么找呢?办法很多,这里的做法是将其与自己的负数相与,就会得出一个唯一一位为1的数了。

回到目录


问题:

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.

Input: numbers={2, 7, 11, 15}, target=9 Output: index1=1, index2=2

大意:

给出一个递增排好序的整型数组,找出两个数组相加等于目标数字。

函数 twoSum 应该返回两个数字的索引,index1 必须小于 index2。请注意你返回的结果(index1 和 index2)不是基于0开始的。

你可以假设每个输入都有一个结果。

输入:numbers={2, 7, 11, 15}, target=9 输出:index1=1, index2=2

思路:

最直接的方法是遍历每一个数,然后看它后面的每个数与它相加能不能等于目标数字,但是这样太慢了。

要利用好数组已经排好序的条件,两个数相加一定是一大一小相加得出目标数字,那么我们可以用两个游标,一个从数组头开始遍历,一个从数组尾开始遍历,如果数组头的数字小于目标数字减去数组尾的数字,则数组头的游标往后移动一格,否则数组尾的游标往前移动一格。如果两个数字相加正好等于目标数字,那么结束循环将结果返回,注意索引要求从1开始,所以我们要将得出得的两个索引号都加一。

举个例子,数组为 [1,2,3,4],目标数字为6,i 和 j 分别一开始在1和4两个数字,因为1小于6-4,所以数组头的游标指向2,数组尾的游标不变,此时2+4正好等于6,返回结果索引为2和4,而不是1和3。

代码(Java):

public class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] result = new int[2];
        int i = 0;
        int j = numbers.length-1;
        while (i < j) {
            if (numbers[i] + numbers[j] == target) {
                result[0] = i+1;
                result[1] = j+1;
                break;
            }
            if (numbers[i] < target - numbers[j]) i++;
            else j--;
        }
        return result;
    }
}

回到目录


问题:

Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.

Find all the elements that appear twice in this array.

Could you do it without extra space and in O(n) runtime?

Example:

Input: [4,3,2,7,8,2,3,1]

Output: [2,3]

大意:

给出一个整型数组, 1 ≤ a[i] ≤ n (n为数组的尺寸),一些元素出现了两次,另一些只出现一次。

找到所有数组中出现了两次的元素。

你能不能不使用额外的空间,在O(n)时间内完成?

例子:

输入: [4,3,2,7,8,2,3,1]

输出: [2,3]

思路:

题目说明了数组中元素的范围,那么可以依此创建一个长度为n的新整型数组,其每个位置的值大小表示对应数字出现的次数,遍历原数组,遇到那个数字就将新数组对应值的位置的元素值加一,就记录下每个数字出现的次数了,之后找出出现次数为2的添加到结果List中即可。

这个做法使用了额外的O(n)的空间,并不符合要求。

代码(Java):

public class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> result = new ArrayList<Integer>();
        int[] count = new int[nums.length+1];
        for (int i = 0; i < nums.length; i++) {
            count[nums[i]]++;
        }
        for (int i = 1; i < count.length; i++) {
            if (count[i] == 2) result.add(i);
        }
        return result;
    }
}

他山之石:

public class Solution {
    // when find a number i, flip the number at position i-1 to negative. 
    // if the number at position i-1 is already negative, i is the number that occurs twice.
    
    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < nums.length; ++i) {
            int index = Math.abs(nums[i])-1;
            if (nums[index] < 0)
                res.add(Math.abs(index+1));
            nums[index] = -nums[index];
        }
        return res;
    }
}

这个做法在注释中也解释了,遍历数组,没遇到一个元素,将其值对应的位置上的那个元素取负数,当然因为元素的值是从1开始的,所以变成位置的时候都要减一,每次换成位置时都要用绝对值来算,因为出现了两次的元素,在之前就已经被变成负数了,所以借此可以判断,如果该位置的元素是个负数,说明之前出现过一次,就记录下来。

这个做法就没有用额外的空间,时间复杂度也是O(n)。

回到目录


问题:

Given an array of n integers where n > 1, nums, return an array output such that output[i] is equal to the product of all the elements of nums except nums[i].

Solve it without division and in O(n).

For example, given [1,2,3,4], return [24,12,8,6].

Follow up: Could you solve it with constant space complexity? (Note: The output array does not count as extra space for the purpose of space complexity analysis.)

大意:

给出一个有n(n>1)个整数的数组nums,返回一个output数组,output[i]等于除了nums[i]外其余所有元素的乘积。

不使用除法且在O(n)时间内完成。

比如,给出 [1,2,3,4],返回 [24,12,8,6]。

进阶: 你能使用固定的空间复杂度吗?(注意:output数组不算做额外的空间。)

思路:

如果用除法就简单了,直接全部乘起来,然后每个位置对应除以nums[i]的元素就可以了。

不用除法的话,我们要用两次遍历,先正着遍历一遍,在结果数组上每个元素都算到累乘至nums数组中对应位置的前面所有的元素,比如第三个元素的值为nums中前连个元素的乘积。

第二次遍历,反着遍历,用一个变量记录从后到前的累乘,同时结果数组中乘以这个变量。

这样对每一个位置来说,其刚好在第一次遍历中取得了它前面所有元素的乘积,第二次遍历中又乘以了它后面所有元素的乘积,唯独不算它自己在内。

代码(Java):

public class Solution {
    public int[] productExceptSelf(int[] nums) {
        int[] result = new int[nums.length];
        
        for (int i = 0; i < result.length; i++) result[i] = 1;
        
        for (int i = 1; i < nums.length; i++) {// 先正着来一遍,只乘到前一个元素
            result[i] = result[i-1] * nums[i-1];
        }
        
        int behind = 1;
        for (int i = nums.length-1; i >= 0; i--) {// 再倒着来一遍,乘以后面的数
            result[i] = result[i] * behind;
            behind = behind * nums[i];
        }
        
        return result;
    }
}

回到目录


问题:

You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Follow up: What if you cannot modify the input lists? In other words, reversing the lists is not allowed.

Example:

Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 8 -> 0 -> 7

大意:

给出两个非空的链表表示的非负整数。最高位的数在第一个,每个节点都只包含一个数字。将两个数相加并作为链表返回。

你可以假设两个数字不包含0开头的数字,除非就是0。

进阶: 你能不改变原有链表吗?也就是说不反转链表。

例子:

输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 输出: 7 -> 8 -> 0 -> 7

思路:

这道题的意思就是由链表表示的两个大数字相加得出结果,比如例子中就是 7243 + 564 = 7807

我们相加两个数字必须要从最低位开始加,所以肯定得先遍历链表知道分别有多少位,还得一位位的往高位加起来,所以我们用两个数组分别记录两个链表各个位的数字和两个链表的位数,然后都从最后一位往前一位位的做加法,注意会有进位,加法过程中还要注意存在某个数位数更多,没有加完的情况,当然也要考虑最后有没有最高一位进位的情况。

在计算结果的时候还是要用数组一位位的记录,最后再换成链表。

这种做法的时间复杂度是O(n),还是比较快的,不过是以空间的消耗为代价。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int[] arr1 = new int[100];
        int index = 0;
        ListNode newl = l1;
        while (newl != null) {
            arr1[index] = newl.val;
            newl = newl.next;
            index ++;
        }
        arr1 = Arrays.copyOfRange(arr1, 0, index);
        
        int[] arr2 = new int[100];
        index = 0;
        newl = l2;
        while (newl != null) {
            arr2[index] = newl.val;
            newl = newl.next;
            index ++;
        } 
        arr2 = Arrays.copyOfRange(arr2, 0, index);
        
        int n1 = arr1.length-1;
        int n2 = arr2.length-1;
        int[] result = new int[100];
        index = 0;
        int flag = 0;
        while (n1 >= 0 && n2 >= 0) {
            int sum = arr1[n1] + arr2[n2] + flag;
            if (sum > 9) {
                flag = 1;
                sum = sum % 10;
            } else flag = 0;
            
            result[index] = sum;
            index ++;
            n1 --;
            n2 --;
        }
        while (n1 >= 0) {
            int sum = arr1[n1] + flag;
            if (sum > 9) {
                flag = 1;
                sum = sum % 10;
            } else flag = 0;
            
            result[index] = sum;
            index ++;
            n1 --;
        }
        while (n2 >= 0) {
            int sum = arr2[n2] + flag;
            if (sum > 9) {
                flag = 1;
                sum = sum % 10;
            } else flag = 0;
            
            result[index] = sum;
            index ++;
            n2 --;
        }
        if (flag == 1) result[index] = 1;
        else index --;
        
        if (index == -1) return null;
        ListNode head = new ListNode(result[index]);
        ListNode nextNode = head;
        while (index > 0) {
            index --;
            ListNode newNext = new ListNode(result[index]);
            nextNode.next = newNext;
            nextNode = newNext;
        }
        
        return head;
    }
}

他山之石:

public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> s1 = new Stack<Integer>();
        Stack<Integer> s2 = new Stack<Integer>();
        
        while(l1 != null) {
            s1.push(l1.val);
            l1 = l1.next;
        };
        while(l2 != null) {
            s2.push(l2.val);
            l2 = l2.next;
        }
        
        int sum = 0;
        ListNode list = new ListNode(0);
        while (!s1.empty() || !s2.empty()) {
            if (!s1.empty()) sum += s1.pop();
            if (!s2.empty()) sum += s2.pop();
            list.val = sum % 10;
            ListNode head = new ListNode(sum / 10);
            head.next = list;
            list = head;
            sum /= 10;
        }
        
        return list.val == 0 ? list.next : list;
    }
}

这个做法差不多,不过是把数组的存储换成栈的存储,刚好符合从最后一位开始加的情况,比要多预留位数的数组更节省空间。

回到目录


问题:

Given a non-empty array of integers, return the k most frequent elements.

For example, Given [1,1,1,2,2,3] and k = 2, return [1,2].

Note:

  • You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
  • Your algorithm's time complexity must be better than O(n log n), where n is the array's size.

大意:

给出一个非空的整型数组,返回最频繁的k个元素。

例子: 给出 [1,1,1,2,2,3] 和 k = 2,返回 [1,2]。

注意:

  • 你可以假设k始终是有效的,1 ≤ k ≤ 不同的元素数。
  • 你的算法时间复杂度要少于O(nlogn),n是数组的尺寸。

思路:

我们要知道哪些数字出现的最多,以及对应的次数,肯定要先遍历一遍并记录下各个数字出现的次数,这里我们用一个HashMap来记录,数字为key,值为它们出现的次数。

接着循环查找出现的最多、次多...的数字,并添加到结果中去。

代码(Java):

public class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        
        Map<Integer, String> map = new HashMap<Integer, String>();
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {// 记录过
                String numStr = map.get(nums[i]);
                int num = Integer.valueOf(numStr).intValue();
                num ++;
                map.put(nums[i], String.valueOf(num));
            } else {// 没记录过
                map.put(nums[i], "1");
            }
        }
        
        int[] keyArr = new int[map.size()];
        int[] valueArr = new int[map.size()];
        int[] used = new int[map.size()];
        int index = 0;
        for (Integer key : map.keySet()) {  
            keyArr[index] = key;
            System.out.println(key);
            valueArr[index] = Integer.valueOf(map.get(key)).intValue();
            System.out.println(map.get(key));
            index ++;
        }  
        
        List<Integer> result = new ArrayList<Integer>();
        while (k > 0) {
            int biggest = 0;
            for (int i = 0; i < used.length; i++) {
                if (used[i] != 1) {
                    biggest = i;
                    break;
                }
            }
            for (int i = 0; i < valueArr.length; i++) {
                if (valueArr[i] > valueArr[biggest] && used[i] != 1) biggest = i;
            }
            result.add(keyArr[biggest]);
            k --;
            used[biggest] = 1;
        }
        
        return result;
        
    }
}

回到目录


问题:

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

大意:

说你有这么一个表示一只股票每日股价的数组。

设计一个算法寻找最大收益。你可以随便完成多少次交易(比如,多次买入卖出)。然而你不能一次进行多次交易(在再次买入前你必须卖出股票)。

思路:

这等于是把股票以后的走向告诉你,让你躺着赚钱,那最大收益当然是低价买进高价卖出了,只不过还是要按照时间顺序,而且每次也只能买一股。

我们遍历股价,遇到不断下降的趋势,我们就等到降到最低了再买,然后等他开始上涨涨到最高点的时候卖出,以此往复,每次的收益累计就是最大收益了。

代码(Java):

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 1) return 0;
        
        int last = prices[0];
        int result = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < last) last = prices[i];
            else if (prices[i] > last) {
                int big = prices[i];
                i ++;
                for (; i < prices.length; i++) {
                    if (prices[i] > big) big = prices[i];
                    else break;
                }
                result += big - last;
                if (i >= prices.length - 1) break;
                else last = prices[i];
            }
        }
        return result;
    }
}

他山之石:

public class Solution {
public int maxProfit(int[] prices) {
    int total = 0;
    for (int i=0; i< prices.length-1; i++) {
        if (prices[i+1]>prices[i]) total += prices[i+1]-prices[i];
    }
    
    return total;
}

其实代码不用我那么麻烦,归根结底还是主要第二天有上涨就买入卖出,相当于把我的一次大操作分解成一天天的小操作,专做短线,想想也是这个理。

回到目录


问题:

Given a non-negative integer n, count all numbers with unique digits, x, where 0 ≤ x < 10n.

Example: Given n = 2, return 91. (The answer should be the total numbers in the range of 0 ≤ x < 100, excluding [11,22,33,44,55,66,77,88,99])

大意:

给出一个非负整数n,计算所有单独数字组成的数 x 的数量,0 ≤ x < 10的n次方。

例子: 给出 n = 2,返回 91。(答案应该包含0 ≤ x < 100范围内的所有数,除了[11,22,33,44,55,66,77,88,99])

思路:

这道题的意思是,对于0到10的n次方内的数,计算其中所有数都唯一的数的数量,比如19每一位都是唯一的数字,而11有两个1重复了,是这个意思,所以当给出n为2时,是计算0到99内的唯一组成数的数量,只有11、22...这些重复的数的组成不唯一,共9个,所以100-9=91。

我们需要找一下规律: 当 n = 0 时,0这个数是唯一组成的。 当 n = 1 时,10个数都是唯一组成的。 当 n = 2 时,除了头10个数外,每个十位上的数只会有一个个位上的数重复的情况,共有1~9,9个十位上的数,每个对应有9个数是唯一组成的,所以是 99。 当 n = 3 时,出了头100个数的结果不影响外,对于三位数,还要排除百位数和其余位的数重复的情况,也就是说原来的99,变成了98,所以9个百位数共有 998 这些数。 当 n = 4 时,除了头1000个数,还有 9987这些数。 。。。 当 n = 10 时,除了前面的数,还有 998765432*1 个数。 当 n >= 11 时,除了前面的数,到后面的数因为位数超过10了,已经不可能不重复了,所以不再有新的唯一组成数出现了。

注意以上计算数量时都要加上前面出现的数,是个累加的过程。

代码(Java):

public class Solution {
    public int countNumbersWithUniqueDigits(int n) {
         if (n == 0) return 1;
         
         int result = 10;
         int middle = 9;
         int decrease = 9;
         while (n > 1 && decrease > 0) {
             middle = middle * decrease;
             result += middle;
             decrease --;
             n--;
         }
         
         return result;
    }
}

回到目录


问题:

The Hamming distance between two integers is the number of positions at which the corresponding bits are different.

Now your job is to find the total Hamming distance between all pairs of the given numbers.

Example:

Input: 4, 14, 2

Output: 6

Explanation: In binary representation, the 4 is 0100, 14 is 1110, and 2 is 0010 (just showing the four bits relevant in this case). So the answer will be: HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.

Note:

  1. Elements of the given array are in the range of 0 to 10^9
  2. Length of the array will not exceed 10^4.

大意:

两个整数之间的Hamming distance是指它们在二进制表示上各个位的数不同的个数。

现在你的工作是找出每对数字之间的Hamming distance的总和。

例子:

输入:4, 14, 2

输出:6

解释:在二进制表示法中,4是 0100,14是 1110,2是 0010(这里只显示四位)。所以答案应该是 HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6。

注意:

  1. 给出的元素范围在0到10的9次方。
  2. 数组的长度不会超过10的4次方。

思路:

这里如果使用双重循环一个个比较两个数字的差异,可以算出来,但是在时间上会有用例超时。

这里换一种思路,我们看每个数都有32位,对每一位,我们统计数组中的数再这一位上为1的有几个数,那么在这一位上,所有两对数不同的情况是为1的数量乘以为0的数量,是个排列组合的问题。对每一位我们都这样操作,就可以很快得出结果了。

代码(Java):

public class Solution {
    public int totalHammingDistance(int[] nums) {
        int result = 0;
        for (int i = 0; i < 32; i++) {
            int tempCount = 0;
            for (int j = 0; j < nums.length; j++) {
                int num = nums[j];
                tempCount += (num >> i) & 1;
            }
            result += tempCount * (nums.length - tempCount);
        }
        return result;
    }
}

回到目录


问题:

A magical string S consists of only '1' and '2' and obeys the following rules:

The string S is magical because concatenating the number of contiguous occurrences of characters '1' and '2' generates the string S itself.

The first few elements of string S is the following: S = "1221121221221121122……"

If we group the consecutive '1's and '2's in S, it will be:

1 22 11 2 1 22 1 22 11 2 11 22 ......

and the occurrences of '1's or '2's in each group are:

1 2 2 1 1 2 1 2 2 1 2 2 ......

You can see that the occurrence sequence above is the S itself.

Given an integer N as input, return the number of '1's in the first N number in the magical string S.

Note: N will not exceed 100,000.

Example 1:

Input: 6

Output: 3

Explanation: The first 6 elements of magical string S is "12211" and it contains three 1's, so return 3.

大意:

一个魔力字符串 S 仅由 1 和 2 组成,遵循下面的规则:

字符串 S 因为其中的1和2由其连续出现的次数组成其自身所以是魔力的。

S前面的一些元素是: S = "1221121221221121122……"

如果我们按照S中1和2出现的次数来分割,就会变成:

1 2 2 1 1 2 1 2 2 1 2 2 ......

你会看到次数的序列正好是S它自身。

给出整数N作为输入,返回魔力字符串S中前N个数内1的个数。

注意:N不会超过100000。

例1:

输入:6

输出:3

解释:S的前六个元素是 "12211" ,包含了三个1,所以返回3。

思路:

其实只要理解题目中字符串S的生成过程,就好模拟这个过程了,1和2交替出现,其字符串自身每个元素代表后面1或者2连续出现的个数,我们需要用数组来记录字符串S,由于需要前面的至少两、三个数做初始值,我们对于小于3的情况直接返回结果,如果给出的n为0,那么返回0,给出的n为1/2/3,都返回1。

然后就可以对记录的数组先初始前面两三个数了,由于要交替出现1和2,所以需要一个布尔变量来记录当前应该放1还是2,还需要一个坐标用来记录我们判断后面数字出现个数的数的位置,开始我设立了两个数组,后来发现两个数组其实都是一模一样的,只需要用一个坐标记录就可以了,一个是在数组尾部添加元素,一个是用来判断添加什么元素的数的位置。然后就可以根据要放1还是要放2来连续放元素了。每次放1的时候记录1的个数,得出结果。

代码(Java):

public class Solution {
    public int magicalString(int n) {
        if (n == 0) return 0;
        else if (n <= 3) return 1;
        
        int[] num = new int[n];
        int[] occ = new int[n];
        int result = 0;
        num[0] = 1;
        
        occ[0] = 1;
        occ[1] = 2;
        result ++;
        boolean one = false;
        int index = 1;
        for (int i = 1; i < n; i++) {
            if (one) {
                for (int j = occ[index]; j > 0; j--) {
                    if (i >= n) break;
                    num[i] = 1;
                    occ[i] = 1;
                    result ++;
                    i++;
                }
                i--;
            } else {
                for (int j = occ[index]; j > 0; j--) {
                    if (i >= n) break;
                    num[i] = 2;
                    occ[i] = 2;
                    i++;
                }
                i--;
            }
            
            one = !one;
            index ++;
        }
        
        // for (int i = 0; i < n; i++) {
        //     System.out.println(num[i]);
        // }
        
        return result;
    }
}

代码改进(Java):

取出多余的数组,只保留一个,节省空间,同时对于放1还是放2的判断可以在一个循环内去做,代码会简洁很多:

public class Solution {
    public int magicalString(int n) {
        if (n == 0) return 0;
        else if (n <= 3) return 1;
        
        int[] num = new int[n];
        int result = 1;
        num[0] = 1;
        num[1] = 2;
        boolean one = false;
        int index = 1;
        for (int i = 1; i < n; i++) {
            for (int j = num[index]; j > 0; j--) {
                if (i >= n) break;
                num[i] = one ? 1 : 2;
                if (one) result ++;
                i++;
            }
            i--;
            
            one = !one;
            index ++;
        }
        
        return result;
    }
}

回到目录


问题:

Now you are given a string S, which represents a software license key which we would like to format. The string S is composed of alphanumerical characters and dashes. The dashes split the alphanumerical characters within the string into groups. (i.e. if there are M dashes, the string is split into M+1 groups). The dashes in the given string are possibly misplaced.

We want each group of characters to be of length K (except for possibly the first group, which could be shorter, but still must contain at least one character). To satisfy this requirement, we will reinsert dashes. Additionally, all the lower case letters in the string must be converted to upper case.

So, you are given a non-empty string S, representing a license key to format, and an integer K. And you need to return the license key formatted according to the description above.

Example 1:

Input: S = "2-4A0r7-4k", K = 4

Output: "24A0-R74K"

Explanation: The string S has been split into two parts, each part has 4 characters.

Example 2:

Input: S = "2-4A0r7-4k", K = 3

Output: "24-A0R-74K"

Explanation: The string S has been split into three parts, each part has 3 characters except the first part as it could be shorter as said above.

Note:

  1. The length of string S will not exceed 12,000, and K is a positive integer.
  2. String S consists only of alphanumerical characters (a-z and/or A-Z and/or 0-9) and dashes(-).
  3. String S is non-empty.

大意:

现在给你一个字符串S,代表我们想要组成的一个软件的序列号。字符串S由数字和字母以及破折号组成。破折号将数字和字母分割成一组组。(比如,如果有M个破折号,则字符串被分为M+1组)。字符串中的破折号可能放错了位置。

我们想要每组字符的长度为K(除了第一组可能短一些,但必须至少包含一个字符)。要满足这些要求,我们会重新插入破折号。此外,所有的小写字母必须转换成大写字母。

所以,给你一个非空字符串S,代表要组成的序列号,以及一个整数K。你需要根据上面的描述返回正确的序列号。

例1:

输入: S = "2-4A0r7-4k", K = 4 输出:"24A0-R74K" 解释:字符串S被分为两组,每组有4个字符。

例2:

输入:S = "2-4A0r7-4k", K = 3 输出:"24-A0R-74K" 解释:字符串S被分为三部分,每部分有三个字符,除了第一部分如上所说可以短一点。

注意:

  1. 字符串S的长度不会超过12000,K是个正数。
  2. 字符串S只由数字及字母(a-z 和/或 A-Z 和/或 0-9)以及破折号(-)组成。
  3. 字符串S非空。

思路:

题目说了一长串,其实总结起来就是:

给一个字符串和正整数,将字符串用破折号分成多个长度为K的组(第一组可以小于K),所有字母必须为大写。

其实还是很容易的,因为第一组不一定长度为K,所以我们从后往前来重组,遇到小写字母就换成大写字母,结果中每放完K个字符就加一个破折号,遍历字符串时遇到破折号直接跳过,为了速度我们使用StringBuffer来处理结果字符串,处理完后再将结果翻转,才是正确的顺序。注意最后可能会在结果的开头出现一个破折号,也就是未翻转前的最后,这时候要去除掉。

代码(Java):

public class Solution {
    public String licenseKeyFormatting(String S, int K) {
        char[] sArr = S.toCharArray();
        String result = "";
        StringBuffer sBuffer = new StringBuffer(result);
        int length = 0;
        for (int i = sArr.length-1; i >= 0; i--) {
            if (sArr[i] - '-' == 0) continue;
            
            if (sArr[i] - 'a' >= 0) sArr[i] = (char)(sArr[i] - 32);
            sBuffer.append(sArr[i]);
            length ++;
            
            if (length == K) {
                sBuffer.append('-');
                length = 0;
            }
        }
        if (sBuffer.length() > 0 && sBuffer.charAt(sBuffer.length()-1) - '-' == 0) sBuffer.delete(sBuffer.length()-1, sBuffer.length());
        sBuffer.reverse();
        result = sBuffer.toString();
        return result;
    }
}

他山之石:

    public String licenseKeyFormatting(String s, int k) {
        StringBuilder sb = new StringBuilder();
        for (int i = s.length() - 1; i >= 0; i--)
            if (s.charAt(i) != '-')
                sb.append(sb.length() % (k + 1) == k ? '-' : "").append(s.charAt(i));
        return sb.reverse().toString().toUpperCase();
    } 

上面的代码时对前面的思路的极致简洁。

回到目录


问题:

Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.

For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).

Note: You may assume that n is not less than 2 and not larger than 58.

大意:

给出一个正整数n,将其拆分成至少两个正整数的和,并使这些数的乘积最大。返回你能获得的最大乘积。

比如,给出 n = 2,返回 1(2 = 1 + 1);给出 n = 10,返回 36(10 = 3 + 3 + 4)。

注意:你可以假设n在2~58之间。

思路:

这道题是要考我们一个个猜拆分数字的和的方法吗?不是的,这种找最大乘积是有规律可循的,结论是拆分成多个2和3相乘得出的乘积最大,至于原因要靠数学分析。

假设将n拆分成相等的多个x相加,那么乘积就是x的n/x次方。

求导数得出 ,当 0 < x < e 时这个导数是正的,当 x = e 时等于0,当 x > e 时为负,也就是说这个乘积会在 x < e 时递增,到达 x = e 时达到最大,接着x越大,乘积变小。所以让 x = e 是最好的,也就是拆分成多个 e ,相乘的结果最大,但是题目要求拆分成正整数,那就只能找和e相近的,那就只能是2和3了,毕竟 2 < e < 3。

而3离e更近,所以我们倾向于多弄出点3来,但是当取到够多的时候就不得不取2了,比如对于 n = 4,22 > 31,也就是说,如果取3使得剩下一个数是1,那么就要放弃取3,而取两个2。

总结就是,将n尽量多拆分成多个3相加,最后如果剩下了4,那就不得不将剩下的4拆分成两个2,此时相乘的乘积一定最大,计算出结果即可。

Leetcode刷到现在,随着难度的提升,已经开始出现需要纯粹依靠高等数学来解决的问题,而不再是单纯的逻辑思考,可见数学的重要性。

代码(Java):

public class Solution {
    public int integerBreak(int n) {
        if (n == 2) return 1;
        else if (n == 3) return 2;
        
        int sum = 0;
        int result = 1;
        while (true) {
            if (n > 4) {
                result = result * 3;
                n -= 3;
            } else {
                result = result * n;
                break;
            }
        }
        
        return result;
    }
}

回到目录


问题:

Given a string s and a string t, check if s is subsequence of t.

You may assume that there is only lower case English letters in both s and t. t is potentially a very long (length ~= 500,000) string, and s is a short string (<=100).

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ace" is a subsequence of "abcde" while "aec" is not).

Example 1: s = "abc", t = "ahbgdc"

Return true.

Example 2: s = "axc", t = "ahbgdc"

Return false.

Follow up: If there are lots of incoming S, say S1, S2, ... , Sk where k >= 1B, and you want to check one by one to see if T has its subsequence. In this scenario, how would you change your code?

大意:

给出字符串s和t,检查s是否是t的子序列。

你可以假设s和t中只有小写英文字母。t可能是个非常长(长度 ~= 5000000)的字符串,s是个短字符串(<=100)。

一个字符串的子序列是将字符串中删除(可以不删除)一些字符,而不改变字符间的相对位置。(比如,“ace”是“abcde”的子序列,但“aec”就不是)。

例1: s = "abc", t = "ahbgdc"

返回 true。

例2: s = "axc", t = "ahbgdc"

返回 false。

进阶: 如果有很多传入的S,称为 S1, S2, ... , Sk ,k>=1B,你想要一个个检查是否是T的子序列。在这个情境下,你会怎么修改你的代码?

思路:

这道题最直接的思路就是遍历t,一个个按顺序检查s中的字符是否顺序出现了,如果一直到s的最后一个字符都出现了,而且是符合顺序的,那就返回true,否则返回false。

但是,这个做法没有用到题目中全是英文小写字母的说明。对于多个子序列检测的情况,同时检测,且多个S之间也需进行一定的比较。

代码(Java):

public class Solution {
    public boolean isSubsequence(String s, String t) {
        if (s.length() == 0) return true;

        int i = 0;
        for (int j = 0; j < t.length(); j++) {
            if (t.charAt(j) - s.charAt(i) == 0) {
                i ++;
                if (i == s.length()) return true;
            }
        }
        return false;
    }
}

他山之石:

public boolean isSubsequence(String s, String t) {
        int fromIndex = 0;
        for (char c : s.toCharArray()) {
            fromIndex = t.indexOf(c, fromIndex);
            if (fromIndex++ < 0) {
                return false;
            }
        }
        return true;
    }
}

这个使用了java的函数indexOf,同时每次从前一个字符找到的位置开始找,本质上与我的做法是一致的,会快一点点。

回到目录


问题:

Given a binary tree, return the inorder traversal of its nodes' values.

For example: Given binary tree [1,null,2,3],

return [1,3,2].

Note: Recursive solution is trivial, could you do it iteratively?

大意:

给出一个二叉树,返回中序遍历的节点值。

比如: 给出二叉树 [1,null,2,3],

返回 [1,3,2]。

注意:递归的解决方法很简单,能不能用循环做?

思路:

所谓中序遍历是指:左中右,这种遍历方式。

对于二叉树,因为要遍历,我们需要记录节点,否则从子节点是无法回到父节点的,所以我们需要使用栈,同时利用其先入后出的特性。

因为要不停地看一个节点的子节点然后回来,又要满足中序遍历,我们用递归来保证深入到叶子节点后能一个个返回来。

因为栈是先入后出的,而我们要记录的顺序是左中右,所以入栈的顺序应该反过来,即右中左,先入右节点,没有右节点了才入根节点,然后对左节点进行同样的操作。

全部遍历完后再一个个出栈记录节点值就可以了。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        if (root == null) return result;
        
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode node = root;
        findNode(node, stack);
        
        while (!stack.isEmpty()) {
            TreeNode temp = stack.pop();
            result.add(temp.val);
        }
        return result;
    }
    
    public void findNode(TreeNode node, Stack<TreeNode> stack) {
        if (node.right != null) {
            findNode(node.right, stack);
            stack.push(node);
        } else {
            stack.push(node);
        }
        if (node.left != null) {
            findNode(node.left, stack);
        }
    }
}

他山之石:

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> list = new ArrayList<Integer>();

    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode cur = root;

    while(cur!=null || !stack.empty()){
        while(cur!=null){
            stack.add(cur);
            cur = cur.left;
        }
        cur = stack.pop();
        list.add(cur.val);
        cur = cur.right;
    }

    return list;
}

这个做法就是不用递归而用循环了,也是用栈,一路入栈左节点到底,然后出栈取值,这时候其实是一个没有左子节点的根节点了,然后对其右节点进行同样的操作,弄完了就返回上一个节点,其实也是左中右的顺序。

回到目录


问题:

Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array.

For example, Given nums = [0, 1, 3] return 2.

Note: Your algorithm should run in linear runtime complexity. Could you implement it using only constant extra space complexity?

大意:

给出一个包含n个数字的数组,数字范围为 0, 1, 2, ..., n,寻找数组遗失的那个数字。

例子: 给出 nums = [0, 1, 3] 返回 2。

注意: 你的算法需要在线性时间复杂度内运行。能不能使用恒定的额外空间复杂度?

思路:

这道题就是找0~n中那个数字没出现。

题目说了要线性时间复杂度,所以不能对数组排序,又说要恒定的空间,所以不能创建一个新数组来记录出现过的数字。

其实既然知道了数字一定是 0, 1, 2, ..., n,只缺一个数字,我们可以求0~n的累加和,然后遍历数组,对数组中的数字也累加个和,相减就知道差的是那个数字了。

代码(Java):

public class Solution {
    public int missingNumber(int[] nums) {
        int total = (1 + nums.length) * nums.length / 2;
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return total - sum;
    }
}

他山之石:

public int missingNumber(int[] nums) {

    int xor = 0, i = 0;
	for (i = 0; i < nums.length; i++) {
		xor = xor ^ i ^ nums[i];
	}

	return xor ^ i;
}

这个方法还是利用异或的特性:相同的数字异或等于0,遍历过程中不断将 i 和数组中的数字异或,最后数组中有的数字都被异或成0了,最后剩下来的就是数组中没有的数字了。

回到目录


问题:

Given four lists A, B, C, D of integer values, compute how many tuples (i, j, k, l) there are such that A[i] + B[j] + C[k] + D[l] is zero.

To make problem a bit easier, all A, B, C, D have same length of N where 0 ≤ N ≤ 500. All integers are in the range of -228 to 228 - 1 and the result is guaranteed to be at most 231 - 1.

Example:

Input: A = [ 1, 2] B = [-2,-1] C = [-1, 2] D = [ 0, 2]

Output: 2

Explanation: The two tuples are:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

大意:

给出四个整数数组A、B、C、D,计算有多少份(i, j, k, l)可以让 A[i] + B[j] + C[k] + D[l] 等于0。

为了让问题简单点,所有的A、B、C、D都有相同的长度N, 0 ≤ N ≤ 500。其中所有的整数都在-2的28次方到2的28次方-1之间,并且结果保证小于2的31次方-1。

例子:

输入: A = [ 1, 2] B = [-2,-1] C = [-1, 2] D = [ 0, 2]

输出: 2

解释: 两种组法为:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

思路:

最简单的方式就是嵌套四个循环把每种情况都试一次,并且记录其中和等于0的次数,这样时间是O(n的四次方)。

我们可以分成两组来计算,一份为A和B中所有可能的元素和,一份是C和D中所有可能的元素和,然后利用map的唯一性,将和作为key,记录A和B的每种和出现的次数,然后计算C和D的所有和时去判断map中有没有这个key的负数,以及出现了几次,累加起来就是最后的四个数相加为0的总次数了。

这里使用了map的getOrDefault(key,default)函数,会在map中查找map对应的值,如果没找到就返回设置的默认值。我们设为0。

代码(Java):

public class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        int result = 0;
        Map<Integer, Integer> map = new HashMap<>();
        
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < B.length; j++) {
                int sum = A[i] + B[j];
                map.put(sum, map.getOrDefault(sum, 0) + 1);
            }
        }
        
        for (int k = 0; k < C.length; k++) {
            for (int l = 0; l < D.length; l++) {
                result += map.getOrDefault(-1 * (C[k] + D[l]), 0);
            }
        }
        
        return result;
    }
}

回到目录


问题:

Given a binary tree, find the leftmost value in the last row of the tree.

Example 1:

Input:

Output: 1

Example 2:

Input:

Output: 7

Note: You may assume the tree (i.e., the given root node) is not NULL.

大意:

给出一个二叉树,找到树最下一行的最左边的节点值。

例1:

输入:

输出: 1

例2:

输入:

输出: 7

注意:你可以假设树(比如,给出根节点)是非空的。

思路:

这道题其实有可以拆解成两个问题:

  1. 找到二叉树的最下面一行;
  2. 在最下面一行找到最左边的节点值。

要注意的是,这个最左边的节点值,并不一定是左节点,也可能是最左边的一个右子节点值。

还记得我们在传送门:LeetCode笔记:102. Binary Tree Level Order Traversal中,是要求将二叉树一层层地输出出来。那么通过同样的方法,我们用BFS广度优先遍历的方法,利用队列,可以确保找到最下一层的所有节点值,然后只需要用一个标记,来在每次梳理一层的节点时,将最左边的一个节点值记录下来,这样,当已经确定是最后一层,没有再下一层后,我们记录下来的就是最下一层的最左边的节点值了。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        
        queue.offer(root);
        int result = root.val;
        boolean has = false;
        while (!queue.isEmpty()) {
            int levelNum = queue.size();
            for (int i = 0; i < levelNum; i++) {
                if (queue.peek().left != null) {
                    queue.offer(queue.peek().left);
                    if (!has) {
                        result = queue.peek().left.val;
                        has = true;
                    }
                }
                if (queue.peek().right != null) {
                    queue.offer(queue.peek().right);
                    if (!has) {
                        result = queue.peek().right.val;
                        has = true;
                    }
                }
                queue.poll();
            }
            has = false;
        }
        
        return result;
    }
}

他山之石:

我的这个方法其实比较慢,我们看看下面这个方法:

public class Solution {
    public int findBottomLeftValue(TreeNode root) {
        return findBottomLeftValue(root, 1, new int[]{0,0});
    }
    public int findBottomLeftValue(TreeNode root, int depth, int[] res) {
        if (res[1]<depth) {res[0]=root.val;res[1]=depth;}
        if (root.left!=null) findBottomLeftValue(root.left, depth+1, res);
        if (root.right!=null) findBottomLeftValue(root.right, depth+1, res);
        return res[0];
    }
}

这个方法的第一个优势就是代码确实比我简洁多了。。。他的做法其实跟我第一个想法差不多,用DFS的方式递归来往下找,同时记录当前找到的节点所在的深度,他用了一个int数组res,数组第一个元素记录节点值,第二个元素记录节点所在的深度。只有在进入更深一层,且这一层还没有记录节点值时,才记录下找到的第一个节点值,其实也就是最左边的节点值,找到后就将深度标记为当前深度,那么后面找到的所有这个深度的节点值都不再记录,除非又找到了更深的节点。这样一直往下,不断根据深度来更新找到的节点值,最后找到的就是最深一层的最左边的节点值了。

回到目录


问题:

You need to find the largest value in each row of a binary tree.

Example:

Input:

Output: [1, 3, 9]

大意:

你需要找到二叉树中每一行最大的值。

例子:

输入:

输出: [1, 3, 9]

思路:

要找每一行最大的数,我们总归是要遍历二叉树的,遍历二叉树分为BFS和DFS,这里我们要找每行最大的数,那么我们就用BFS,一行行分析过去。

还记得我们在传送门:LeetCode笔记:102. Binary Tree Level Order Traversal中,是要求将二叉树一层层地输出出来。那么通过同样的方法,我们用利用队列的先进先出特性,依次保留每一行新读进来的节点。用一个变量记录当前行的总节点数,然后对每一行都去寻找最大的节点值是多少,记录在结果中就可以了。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<Integer> largestValues(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        
        List<Integer> res = new LinkedList<Integer>();
        if (root == null) return res;
        
        queue.offer(root);
        while (!queue.isEmpty()) {
            int levelNum = queue.size();
            int temp = queue.peek().val;
            if (queue.peek().left != null) queue.offer(queue.peek().left);
            if (queue.peek().right != null) queue.offer(queue.peek().right);
            queue.poll();
            for (int i = 1; i < levelNum; i++) {
                if (queue.peek().val > temp) temp = queue.peek().val;
                if (queue.peek().left != null) queue.offer(queue.peek().left);
                if (queue.peek().right != null) queue.offer(queue.peek().right);
                queue.poll();
            }
            res.add(temp);
        }
        return res;
    }
}

他山之石:

除了用BFS,其实DFS也可以做,但是就需要有一个参数来记录当前节点所在的行,同时对于每次遇到的新节点,判断该节点值与已经记录的所在行的最大值之间的大小,如果更大就替换掉结果中记录的值,如果小一些那就略过。这个方法运行起来会比我的方法要快。

public class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        helper(root, res, 0);
        return res;
    }
    private void helper(TreeNode root, List<Integer> res, int d){
        if(root == null){
            return;
        }
       //expand list size
        if(d == res.size()){
            res.add(root.val);
        }
        else{
        //or set value
            res.set(d, Math.max(res.get(d), root.val));
        }
        helper(root.left, res, d+1);
        helper(root.right, res, d+1);
    }
}

回到目录


问题:

Suppose you have N integers from 1 to N. We define a beautiful arrangement as an array that is constructed by these N numbers successfully if one of the following is true for the ith position (1 ≤ i ≤ N) in this array:

  1. The number at the ith position is divisible by i.
  2. i is divisible by the number at the ith position. Now given N, how many beautiful arrangements can you construct? Example 1:

Input: 2

Output: 2

Explanation:

The first beautiful arrangement is [1, 2]:

Number at the 1st position (i=1) is 1, and 1 is divisible by i (i=1).

Number at the 2nd position (i=2) is 2, and 2 is divisible by i (i=2).

The second beautiful arrangement is [2, 1]:

Number at the 1st position (i=1) is 2, and 2 is divisible by i (i=1).

Number at the 2nd position (i=2) is 1, and i (i=2) is divisible by 1.

Note:

  1. N is a positive integer and will not exceed 15.

大意:

假设你有1到N的N个整数,我们定义如果这N个整数可以组成数组后每第 i 位(1 ≤ i ≤ N)都满足下面两个要求之一就称其为漂亮的安排:

  1. 第 i 个位置的数字可以被 i 整除。
  2. i 可以被第 i 个位置的数字整除。 现在给出N,你可以组成多少种漂亮的安排? 例 1:

输入: 2

输出: 2

解释:

第一个漂亮的安排是 [1, 2]:

第一个位置(i = 1)的数字是 1,而 1 可以被 i (i = 1)整除。

第二个位置(i = 2)的数字是 2,而 2 可以被 i(i = 2)整除。

第二个漂亮的安排是 [2, 1]:

第一个位置(i = 1)的数字是 2,而 2 可以被 i (i = 1)整除。

第二个位置(i = 2)的数字是 1,而 1 可以被 i(i = 2)整除。

注意:

  1. N是个正数,而且不会超过15。

思路:

乍一想有很多种可能不知道怎么统计,而这恰恰就可以通过递归回溯来实现。

我们的思路是,从第一位到第N位,我们都要找到对应的没有放置过的数字来放,每一位都会有很多个数字可以放,而放了之后以后就不能放了,这样一直放到最后一位,如果都能放到数字,那就是一种漂亮的安排,总结果就要加一。

这种思路就可以通过递归来实现。每一次递归我们都判断当前位置有哪些没放过的数字可以放,对于数字有没有放过我们需要一个数字来记录。对于每个放在这一位的数字,都是一种可能性,我们要继续往后递归看能不能全部放完才能知道要不要算作一种。如果所有都放完了那就算作一种了,总结过可以加一。

要注意这里的位置并不是从0开始算的,而是1。

代码(Java):

public class Solution {
    public int countArrangement(int N) {
        int[] num = new int[N];
        int res = findWay(num, 1);
        return res;
    }
    
    public int findWay(int[] num, int index) {
        if (index == num.length+1) return 1;
        int total = 0;
        for (int i = 0; i < num.length; i++) {
            if (num[i] != 1) {
                if ((i+1) % index == 0 || index % (i+1) == 0) {
                    int[] newNum = num.clone();
                    newNum[i] = 1;
                    total += findWay(newNum, index+1);
                }
            }
        }
        return total;
    }
}

回到目录


问题:

Given the root of a tree, you are asked to find the most frequent subtree sum. The subtree sum of a node is defined as the sum of all the node values formed by the subtree rooted at that node (including the node itself). So what is the most frequent subtree sum value? If there is a tie, return all the values with the highest frequency in any order.

Examples 1

Input:

return [2, -3, 4], since all the values happen only once, return all of them in any order.

Examples 2

Input:

return [2], since 2 happens twice, however -5 only occur once.

Note: You may assume the sum of values in any subtree is in the range of 32-bit signed integer.

大意:

给出一个树的根节点,要求你找到出现最频繁的子树和。一个节点的子树和是指其所有子节点以及子节点的子节点的值之和(包含节点本身)。所以最频繁的子树和是什么?如果有并列的,返回所有最高频率的值,顺序不限。

例1:

输入:

返回 [2, -3, 4],因为所有值都只出现了一次,以任意顺序返回它们。

例2:

输入:

返回 [2],因为2这个和出现了两次,而 -5 只出现了一次。

注意:你可以假设所有子树的和都在32位int型范围内。

思路:

要计算一个节点的子树和其实不难,只需要用递归不断判断其子节点有没有左右子节点,有的话就加起来其值就好了。

但是这道题要比较所有节点的子树和,那就要求每遇到一个节点,都要以这个节点为根节点,计算其子树和,所以每次递归时都要计算新计算一次。

那怎么记录所有子树和呢?这道题既然是要求找出出现频率最高的子树和值,那肯定要记录各个值出现的次数,方法也就呼之欲出了,用HashMap,以子树和为key,以出现次数为value,对于已经出现过的子树和,就将其value+1,没出现过的就添加到HashMap中去,其value设为1。这样就可以边计算所有子树和,边记录各个和出现的次数了。

现在只剩下一个问题,找到出现最频繁的子树和,而且不一定只有一个子树和值。所以我们要遍历HashMap,记录出现的次数最大的子树和,因为可能有多个,我们用数组来记录,如果碰到次数更当前记录的次数最大的一直的子树和,就添加到数组中,当出现更大次数的时候就重新记录,替代数组第一个元素,同时用一个int型变量num来记录最大出现频率下有几个子树和,可以保证遍历HashMap完后前num个数组元素是要的结果,我们取这几个就可以了。

这个做法已经算快的了,打败了91%。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    
    public int[] findFrequentTreeSum(TreeNode root) {
        if (root == null) return new int[0];
        
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        countSum(root, map);
        
        int[] all = new int[map.size()];
        int num = 0;
        int big = 0;
        Iterator iter = map.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            if ((int)entry.getValue() > big) {
                all[0] = (int)entry.getKey();
                num = 1;
                big = (int)entry.getValue();
            } else if ((int)entry.getValue() == big) {
                all[num] = (int)entry.getKey();
                num++;
            }
        }
        return Arrays.copyOfRange(all, 0, num);
    }
    
    public int countSum(TreeNode root, HashMap<Integer, Integer> map) {
        int sum = 0;
        sum += root.val;
        if (root.left != null) sum += countSum(root.left, map);
        if (root.right != null) sum += countSum(root.right, map);
        
        if (map.get(sum) != null) {// 之前放过
            map.put(sum, map.get(sum)+1);
        } else {
            map.put(sum, 1);
        }
        return sum;
    }
}

回到目录


问题:

Given a circular array (the next element of the last element is the first element of the array), print the Next Greater Number for every element. The Next Greater Number of a number x is the first greater number to its traversing-order next in the array, which means you could search circularly to find its next greater number. If it doesn't exist, output -1 for this number. Example 1:

Input: [1,2,1]

Output: [2,-1,2]

Explanation: The first 1's next greater number is 2;

The number 2 can't find next greater number;

The second 1's next greater number needs to search circularly, which is also 2.

Note: The length of given array won't exceed 10000.

大意:

给出一个循环数组(最后一个元素的下一个元素是数组的第一个元素),打印每个元素的下一个更大的数字。一个元素的下一个更大的数字是在数组中往后遍历遇到的第一个比他大的数字,也就意味着你可以循环搜索数组来寻找下一个更大的数字。如果不存在,为这个数字输出 -1。 例 1:

输入:[1,2,1]

输出:[2,-1,2]

解释:第一个 1 的下一个更大数字是 2。

数字 2 找不到下一个更大的数字。

第二个 1 的下一个更大的数字需要循环寻找,也是 2。

注意:给出的数组长度不会超过 10000。

思路:

首先,最简单的笨办法,对每个元素都往后遍历寻找第一个比他大的数字记录下来,找不到就是 -1。

代码(Java):

public class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int[] res = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            boolean find = false;
            int j = i + 1;
            for (int k = 0; k < nums.length; k++) {
                if (j >= nums.length) j = 0;
                if (nums[j] > nums[i]) {
                    find = true;
                    res[i] = nums[j];
                    break;
                }
                j++;
            }
            if (!find) res[i] = -1;
        }
        return res;
    }
}

他山之石:

我们思考一下,在遍历数组的过程中,如果是往后遇到大的数,那就是第一个更大的数,如果一直遇到不断小的数,才会一直找不到,我们可以用一个栈来记录,遇到比栈顶小的数字就放入栈中,遇到比栈顶大的数字就说明这是栈顶数字的下一个更大的数,就将其放在结果数组的对应位置上,栈顶的元素出栈,继续比较新的栈顶的数,如果还是大,那么继续记录,出栈,直到栈顶的数比新数要小,那么就可以将新数入栈了。因为我们要将找到的更大的数放在对应位置上,所以栈中记录的应该是元素位置,而不是具体的数字,但比较的时候还是比较原来的数组中这个位置的数字,这一点要想清楚。此外,因为会出现循环寻找的情况,所以数组我们可能遍历两次。这个做法会快很多。

public class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n = nums.length, next[] = new int[n];
        Arrays.fill(next, -1);
        Stack<Integer> stack = new Stack<>(); // index stack
        for (int i = 0; i < n * 2; i++) {
            int num = nums[i % n]; 
            while (!stack.isEmpty() && nums[stack.peek()] < num)
                next[stack.pop()] = num;
            if (i < n) stack.push(i);
        }   
        return next;
    }
}

回到目录


问题:

Given a matrix of M x N elements (M rows, N columns), return all elements of the matrix in diagonal order as shown in the below image. Example:

Input:

[

[ 1, 2, 3 ],

[ 4, 5, 6 ],

[ 7, 8, 9 ]

]

Output: [1,2,4,7,5,3,6,8,9]

Explanation:

Note: The total number of elements of the given matrix will not exceed 10,000.

大意:

给出一个包含 M * N 个元素的矩阵(M行,N列),按照如下图所示顺序返回矩阵中所有元素。 例子:

输入:

[

[ 1, 2, 3 ],

[ 4, 5, 6 ],

[ 7, 8, 9 ]

]

输出:[1,2,4,7,5,3,6,8,9]

解释:

注意: 矩阵的元素数量不会超过10000。

思路:

我们可以跟随例子中的路线来遍历矩阵,路线无非就是从左下到右上,到顶后又从右上到坐下,一直到最右下角一个点。

在往右上的过程中,一般是行在减,列在加,有三种情况停止右上:

  1. 到了第一行,不能再往上了;
  2. 到了最右边列,不能再往右了;
  3. 到了最右下角的元素,这时候要全部结束遍历。

往左下的过程中,一般是行在加,列在减,有三种情况停止左下:

  1. 到了第一列,不能在往左了;
  2. 到了最下边的行,不能再往下了;
  3. 到了最右下角的元素,这时候要全部结束遍历。

我们把这个过程用代码实现出来就可以了,用多个 if - else 来分支处理。

代码(Java):

public class Solution {
    public int[] findDiagonalOrder(int[][] matrix) {
        boolean up = true;
        if (matrix.length == 0) return new int[0];
        int[] res = new int[matrix.length * matrix[0].length];
        int i = 0;
        int j = 0;
        for (int k = 0; k < matrix.length * matrix[0].length; k++) {
            res[k] = matrix[i][j];
            if (up) {// 往右上走
                if ((i-1) >= 0 && (j+1) < matrix[0].length) {
                    i--;
                    j++;
                } else if ((j+1) < matrix[0].length) {
                    j++;
                    up = false;
                } else if ((i+1) < matrix.length) {
                    i++;
                    up = false;
                } else break;
            } else {// 往左下走
                if ((i+1) < matrix.length && (j-1) >= 0) {
                    i++;
                    j--;
                } else if ((i+1) < matrix.length) {
                    i++;
                    up = true;
                } else if ((j+1) < matrix[0].length) {
                    j++;
                    up = true;
                } else break;
            }
        }
        return res;
    }
}

回到目录


问题:

Given a list of 24-hour clock time points in "Hour:Minutes" format, find the minimum minutes difference between any two time points in the list.

Example 1:

Input: ["23:59","00:00"]

Output: 1

Note:

  1. The number of time points in the given list is at least 2 and won't exceed 20000.
  2. The input time is legal and ranges from 00:00 to 23:59.

大意:

给出一个 "Hour:Minutes" 形式的24小时制时间点的List,寻找List中任意两个时间点的最小分钟时间差。

例1:

输入:["23:59","00:00"]

输出:1

注意:

  1. 给出的List中包含的时间点至少有两个,不超过20000。
  2. 输入的时间是合法的,而且范围在 00:00 到 23:59。

思路:

题目会给出一系列24小时制的时间,我们要找到最小的两个时间的时间差,这个差值是以分钟数表示的,为了计算方便,我们写一个函数来将所有给出的24小时制时间全部改成分钟表示,比如 1:30 用全分钟数来表示就90分钟,这样我们计算时间差就很方便,要排序也很方便。

全部转换成分钟数后,我们放在一个int型数组里,对数组排序,这样我们就可以按照拍完序后的顺序去两两比较时间点之间的时间差,看哪个时间差最小,记录下来,要注意的一点是最后一个时间要用24小时的分钟数减去他然后加上第一个时间点的时间差,得到最后一个时间点和第一个时间点的时间差。

题目说了至少会有两个时间点,所以给的List为空的情况不用考虑。

代码(Java):

public class Solution {
    public int findMinDifference(List<String> timePoints) {
        int[] minuteArr = new int[timePoints.size()];
        for (int i = 0; i < minuteArr.length; i++) {
            minuteArr[i] = transToMinute(timePoints.get(i));
        }
        Arrays.sort(minuteArr);
        
        int res = 24*60 - minuteArr[minuteArr.length-1] + minuteArr[0];
        for (int i = 0; i < minuteArr.length-1; i++) {
            if (minuteArr[i+1] - minuteArr[i] < res) res = minuteArr[i+1] - minuteArr[i];
        }
        return res;
    }
    
    public int transToMinute(String time) {
        String[] arr = time.split(":");
        int a = Integer.valueOf(arr[0]).intValue() * 60;
        int b = Integer.valueOf(arr[1]).intValue();
        return a + b;
    }
}

回到目录


问题:

Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from either end of the array followed by the player 2 and then player 1 and so on. Each time a player picks a number, that number will not be available for the next player. This continues until all the scores have been chosen. The player with the maximum score wins.

Given an array of scores, predict whether player 1 is the winner. You can assume each player plays to maximize his score.

Example 1:

Input: [1, 5, 2]

Output: False

Explanation: Initially, player 1 can choose between 1 and 2.

If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2).

So, final score of player 1 is 1 + 2 = 3, and player 2 is 5.

Hence, player 1 will never be the winner and you need to return False.

Example 2:

Input: [1, 5, 233, 7]

Output: True

Explanation: Player 1 first chooses 1. Then player 2 have to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233.

Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win.

Note:

  1. 1 <= length of the array <= 20.
  2. Any scores in the given array are non-negative integers and will not exceed 10,000,000.
  3. If the scores of both players are equal, then player 1 is still the winner.

大意:

给出一个非负整数数组表示分数。玩家1从数组的第一个或者最后一个分数选择一个,接着玩家2在剩下的分数里继续这样选择,然后又玩家1选择,如此往复。每次由一个玩家选择,选择的数字下一个玩家不能再选。直到所有元素都被选择完。总分数更大的玩家获胜。

给出分数数组,预测玩家1是否是赢家。你可以假设每个玩家都尽量扩大他的分数。

例1:

输入:[1, 5, 2]

输出:False

解释:一开始,玩家1可以选择1或者2。

如果他选择2(或者1),玩家2可以选择1(或者2)和5,然后玩家1可以选剩下的1(或者2)。

所以,最后玩家1的分数是 1+2=3,玩家2是5。

因此,玩家1永远不会是赢家,你需要返回False。

例2:

输入:[1, 5, 233, 7]

输出:True

解释:玩家1首选选择1。玩家2需要选择5或者7。无论玩家2选择什么,玩家1都可以选233。

最终,玩家1(234)比玩家2(12)的分数更高,所以你需要返回True代表玩家1赢。

注意:

  1. 1 <= 数组的长度 <= 20。
  2. 给出的数组中所有数字都是非负整数,而且不会超过10000000。
  3. 如果两个玩家的分数相同,还是判玩家1胜。

思路:

这个如果要穷举所有可能的选法实在是太多了,而且也不是贪心算法,每次都取当前最大值,因为要考虑极大数的位置,比如例2的233。因此我们只能用递归来做。

假设所有分数的总和为sum,那么最后一定是玩家1选择了一部分,玩家2选择了另一部分,我们只需要玩家1的分数大于等于玩家2就可以了,那么可以想象成,每次玩家1选择一个分数,就是加一个分数,轮到玩家2选择时,就是减去一个分数,判断最后剩下的数字的正负就可以知道玩家1是否赢了。

我们另外创建一个函数,用来递归计算每次的加分数和减分数,最终值的正负就是赢与否,注意题目说分数相等也是玩家1赢,所以最后如果等于0,也是玩家1赢。

他山之石(Java):

public class Solution {
    public boolean PredictTheWinner(int[] nums) {
        return findAns(nums, 0, nums.length-1) >= 0;
    }
    
    public int findAns(int[] nums, int i, int j) {
        if (i == j) return nums[i];
        else {
            int first = nums[i] - findAns(nums, i+1, j);
            int last = nums[j] - findAns(nums, i, j-1);
            return Math.max(first, last);
        }
    }
}

回到目录


问题:

You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol.

Find out how many ways to assign symbols to make sum of integers equal to target S.

Example 1:

Input: nums is [1, 1, 1, 1, 1], S is 3.

Output: 5

Explanation:

-1+1+1+1+1 = 3

+1-1+1+1+1 = 3

+1+1-1+1+1 = 3

+1+1+1-1+1 = 3

+1+1+1+1-1 = 3

There are 5 ways to assign symbols to make the sum of nums be target 3.

Note:

  1. The length of the given array is positive and will not exceed 20.
  2. The sum of elements in the given array will not exceed 1000.
  3. Your output answer is guaranteed to be fitted in a 32-bit integer.

大意:

给你一个非负整数组成的数组和目标数S。现在你有两个符号 + 和 - 。对每个整数,你要选择 + 和 - 之一作为它的符号。

寻找有多少种加符号的方式让这些整数的和为目标数S。

例1:

输入:nums 是 [1, 1, 1, 1, 1], S 是 3。

输出:5

解释:

-1+1+1+1+1 = 3

+1-1+1+1+1 = 3

+1+1-1+1+1 = 3

+1+1+1-1+1 = 3

+1+1+1+1-1 = 3

有五种加符号的方式让总和等于目标数3。

注意:

  1. 给出的数组长度是证书且不超过20。
  2. 给出的数组元素总和不超过1000。
  3. 你输出的答案保证是32位int型的。

思路:

这个问题其实可以分解为两个问题:

  1. 计算加上符号后正数或者负数之和应该为多少;
  2. 用数组中的数有多少种方法可以加起来等于上面计算出的和。

对于第一个问题,我们来分析一下。由于只有正负两种符号,最后分配符号后数组中的元素可以分为整数之和与负数之和,他们两个相加等于目标数,即:

sum(正) - sum(负) = target

两边都加上同样的sum(正) + sum(负):

sum(正) + sum(负) + sum(正) - sum(负) = target + sum(正) + sum(负)

化简得:

2 * sum(正) = target + sum(正) + sum(负) = target + sum(数组总和)

那么我们可以惊讶的得出一个结论,都加上符号后,所有正数的和等于目标数加上数组所有元素之和。通过这个公式我们首先可以简单的判断出找不到结果的情况,也就是数组总和小于目标数或者目标数加上数组所有元素之和除以2不能整除时,都无法找到分配符号的方法。而且最后所有分配了 + 的元素之和一定等于目标数加上数组所有元素之和的一半。

现在我们知道所有正数相加应该等于多少了,剩下的就是第二个问题,使用数组中的元素有多少种方法相加得到这个正数和?

这里我们用一种非常巧妙的记录方式:对于每个元素,我们看看他与正数和只差是多少,这个结果处有没有其余的元素,没有我们就减一看看有没有其他元素,没有继续减一,直到见到0,这时候其实就是它自己了。对下一个元素依然这样判断。我们用一个标记来记录从0到正数和之间每个数当前用别的元素相加后能得到的个数,最后遍历完所有元素后,看看正数和记录了多少种其余元素相加得到的次数,就是我们要的方法数了。

代码(Java):

public class Solution {
    public int findTargetSumWays(int[] nums, int s) {
        int sum = 0;
        for (int n : nums)
            sum += n;
        // 两种情况找不到结果,找得到的话就用subsetSum去找,证书和是(s + sum) >>> 1,也就是除以2
        return sum < s || (s + sum) % 2 > 0 ? 0 : subsetSum(nums, (s + sum) >>> 1); 
    }   

    public int subsetSum(int[] nums, int s) {
        int[] dp = new int[s + 1]; 
        dp[0] = 1;// 初始记录0的位置为1
        for (int n : nums)
            // 对每个元素,看看他现有能和别的元素相加得到哪些位置的数
            for (int i = s; i >= n; i--)
                dp[i] += dp[i - n]; 
        return dp[s];
    } 
}

回到目录


问题:

Given a binary tree, return the preorder traversal of its nodes' values.

For example:

Given binary tree {1,#,2,3},

return [1,2,3].

Note: Recursive solution is trivial, could you do it iteratively?

大意:

给出一个二叉树,返回节点值的前序遍历。

比如:

给出二叉树 {1,#,2,3},

返回 [1,2,3]。

注意:递归实现很简单,你能用循环来做吗?

思路:

前序遍历二叉树,顺序是根节点 -> 左子节点 -> 右子节点。

如题所说,用递归来做很简单,对于每个节点判断其有没有左子节点,有就将其值加入结果,然后递归判断其左子节点。之后再判断一个节点的右子节点。递归着就能全部按顺序获取到。

题目要求用循环做。前序遍历始终保证左子节点在右子节点之前,所以这是一个DFS,我们利用栈来做。

我们先将根节点入栈(注意判断有无根节点),只要栈不空,我们就一直循环。判断当前栈顶元素有无左子节点,有的话我们将其值加到结果List中,然后将左子节点入栈,别急,这之前还有一个操作,为了避免死循环,我们发现有左子节点,并且取到了左子节点的值后,原来的根节点就不需要了,否则每次判断到这个根节点就又进入其左子节点,重复操作了,因此我们在这里将这个根节点的值和它右子节点的指针赋给一个新节点,将原来的根节点出栈不要了,将新节点入栈,然后再入栈得到的左子节点,因为我们之后只会用到这个根节点的右子节点。

如果没有左子节点(本来就没有,或者是已经操作过了),那么就判断有无右子节点,有的话就取其值加到结果List中,然后这个根节点就可以不要了,直接出栈,将右子节点入栈。

如果两个子节点都没有,那就直接出栈吧。

注意我们对于每个节点的值,都是一开始碰到了就加到结果List中去,而不是等处理完了它所有子节点后再加,这是为了保证前序遍历的顺序。

代码(Java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        if (root == null) return new LinkedList<Integer>();
        List<Integer> res = new LinkedList<Integer>();
        Stack<TreeNode> stack = new Stack<TreeNode>();
        stack.push(root);
        res.add(root.val);
        while (!stack.isEmpty()) {
            if (stack.peek().left != null) {
                res.add(stack.peek().left.val);
                TreeNode temp = new TreeNode(stack.peek().val);
                temp.right = stack.peek().right;
                TreeNode left = stack.pop().left;
                stack.push(temp);
                stack.push(left);
            } else if (stack.peek().right != null) {
                res.add(stack.peek().right.val);
                TreeNode temp = stack.pop();
                stack.push(temp.right);
            } else {
                stack.pop();
            }
        }
        return res;
    }
}

回到目录


问题:

Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix.

Note that it is the kth smallest element in the sorted order, not the kth distinct element.

Example:

k = 8,

return 13.

Note:

You may assume k is always valid, 1 ≤ k ≤ n2.

大意:

给出一个 n * n的矩阵,每一行每一列都是升序的,找到矩阵中第 k 小的元素。

注意是整个顺序第 k 小的数,不是第 k 个元素。

例:

image.png

k = 8,

返回 13。

注意:

你可以假设 k 始终有效,1 ≤ k ≤ n2。

思路:

题目给出的矩阵只是每一行和每一列是升序的,但是一个元素的下一个行元素和下一个列元素之间的大小是不定的。

我们要找第 k 小的元素,那么用一个 k 遍的循环来从小开始找。

我们用一个数组来记录每行现在前多少个元素已经记录过了,当前要找的时候从这一行第几个元素开始找,不过要注意如果这一行都找完了就不找了。

每次找当前最小值的时候都从这一行的当前该找的位置开始,这个位置可能每一行都是不同的,找到最小的记录下来,就是这一轮找到最小的数。一直到第 k 轮找到的最小的数就是我们要的结果。

代码(Java):

public class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        if (matrix.length == 0 || matrix[0].length == 0) return 0;
        int[] index = new int[matrix.length];
        int pos = 0;
        int small = matrix[matrix.length-1][matrix[0].length-1];;
        for (; k > 0; k--) {
            small = matrix[matrix.length-1][matrix[0].length-1];
            for (int i = 0; i < matrix.length; i++) {
                if (index[i] >= matrix[i].length) continue;
                if (matrix[i][index[i]] <= small) {
                    small = matrix[i][index[i]];
                    pos = i;
                }
            }
            index[pos]++;
        }
        return small;
    }
}

回到目录


问题:

Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.

Example 1:

Input: k = 3, n = 7

Output:

[[1,2,4]]

Example 2:

Input: k = 3, n = 9

Output:

[[1,2,6], [1,3,5], [2,3,4]]

大意:

找到所有可能的k个数字相加得到n的组合,只有1到9这些数字可以用,并且组合中的每个数字必须都只使用一次。

例1:

输入:k = 3,n = 7

输出:

[[1,2,4]]

例2:

输入:k = 3,n = 9

输出:

[[1,2,6], [1,3,5], [2,3,4]]

思路:

题目有几个要求,数字个数必须为k,只能用1到9这些数字,每个组合里数字不能重复,数字相加为n。

因为可能性有很多,我们用递归来做。

因为只能用1到9的数字,我们就从1到9分别遍历,依次以某个数字开始,往后找进行加数字,每递归一次,后面还有多少个数字就有多少种组合的方法,所以其实组合的方法有987*6....每次递归我们判断成功的条件是目前加起来的和正好等于目的数字n,且组合中的数字个数正好为k。如果我们用到的数字超过9了或者个数超过k了,就不要继续递归了。注意每次递归时因为要循环使用参数,所以我们每次都要创建新的变量来进行操作,避免影响参数本身的值。

代码(Java):

public class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        
        for (int i = 1; i <= 9; i++) {
            int sum = i;
            List<Integer> list = new ArrayList<Integer>();
            list.add(i);
            find(k-1, n, list, sum, i+1, res);
        }
        
        return res;
    }
    
    public void find(int k, int n, List<Integer> list, int sum, int start, List<List<Integer>> res) {
        if (sum == n && k == 0) {
            res.add(list);
        } else if (sum < n) {
            if (k <= 0 || start > 9) return;
            else {
                for (int i = start; i <= 9; i++) {
                    int newSum = sum;
                    newSum += i;
                    List<Integer> newList = new ArrayList<Integer>();
                    newList.addAll(list);
                    newList.add(i);
                    find(k-1, n, newList, newSum, i+1, res);
                }
            }
        }
    }
}

回到目录


问题:

Given a non-empty string containing an out-of-order English representation of digits 0-9, output the digits in ascending order.

Note:

1、Input contains only lowercase English letters. 2、Input is guaranteed to be valid and can be transformed to its original digits. That means invalid inputs such as "abc" or "zerone" are not permitted. 3、Input length is less than 50,000.

Example 1:

Input: "owoztneoer"

Output: "012"

Example 2:

Input: "fviefuro"

Output: "45"

大意:

给出一个非空字符串,由数字0~9的英文单词的乱序组成,以升序的方式输出所有的数字。

注意: 1、输入只包含小写英文字母。 2、输入保证是有效的而且可以被转换成原始数字。也就是说无效的输入比如“abc”或者“zerone”是不允许的。 3、输入长度小于5000。

例1:

输入:“owoztneoer”

输出:“012”

例2:

输入:“fviefuro”

输出:“45”

思路:

题目的意思是会将多个数字的英文单词的字母打乱放置。

首先我们看看0~9数字对应的单词:“zero”、“one”、“two”、“three”、“four”、“five”、“six”、“seven”、“eight”、“nine”。

在这些单词中,我们先找到唯一存在的字母,这样就可以根据这些字母直接推断出存在该数字的英文单词。唯一存在的字母有:0的‘z’、2的‘w’、4的‘u’、6的‘x’、8的‘g’,如果我们的字符串中有这些字母,那一定有这些数字,从而可以将相应的单词中的字母全部清除一遍。

上面唯一的全部找完之后,剩下1、3、5、7、9,这些本身没有唯一的字母,但是当上一批清除干净后,就又存在一些唯一的字母了:1的‘o’、3的‘t’、5的‘f’。从而又可以根据这些目前唯一的字母清除一批数字的所有字母。

这时,剩下的7、9之间也有一些唯一的字母了,如7的‘s’、9的‘n’。从而也可以找出他们来。

要注意的是,每个数字并不是只会出现一次,而由于我们后面的数字的寻找需要将前面唯一的数字清楚干净,所以我们一个是要按照顺序来清,另一个是要循环将一个数字全部清干净了再清下一个。

在实现过程中我第一个做法是直接对字符串进行操作,截取字符串来清除字母,但这个做法其实会很慢,即使用了StringBuffer也很慢,对于大量数据来说时就超时了。要注意题目说了所有字母都是小写字母,我们其实可以用一个26位数字来记录每个字母出现了几次,在清楚字母时直接将对应位置的值减一就可以了,这样对数组的操作会快很多。

代码(Java):

public class Solution {
    public String originalDigits(String s) {
        int[] num = new int[10];
        int[] totalChar = new int[26];
        for (int i = 0; i < s.length(); i++) {
            totalChar[s.charAt(i) - 'a']++;
        }
        
        while (totalChar['z'-'a'] > 0) {
            num[0]++;
            totalChar['z'-'a']--;
            totalChar['e'-'a']--;
            totalChar['r'-'a']--;
            totalChar['o'-'a']--;
        }
        while (totalChar['w'-'a'] > 0) {
            num[2]++;
            totalChar['t'-'a']--;
            totalChar['w'-'a']--;
            totalChar['o'-'a']--;
        }
        while (totalChar['u'-'a'] > 0) {
            num[4]++;
            totalChar['f'-'a']--;
            totalChar['o'-'a']--;
            totalChar['u'-'a']--;
            totalChar['r'-'a']--;
        }
        while (totalChar['x'-'a'] > 0) {
            num[6]++;
            totalChar['s'-'a']--;
            totalChar['i'-'a']--;
            totalChar['x'-'a']--;
        }
        while (totalChar['g'-'a'] > 0) {
            num[8]++;
            totalChar['e'-'a']--;
            totalChar['i'-'a']--;
            totalChar['g'-'a']--;
            totalChar['h'-'a']--;
            totalChar['t'-'a']--;
        }
        while (totalChar['o'-'a'] > 0) {
            num[1]++;
            totalChar['o'-'a']--;
            totalChar['n'-'a']--;
            totalChar['e'-'a']--;
        }
        while (totalChar['t'-'a'] > 0) {
            num[3]++;
            totalChar['t'-'a']--;
            totalChar['h'-'a']--;
            totalChar['r'-'a']--;
            totalChar['e'-'a']--;
            totalChar['e'-'a']--;
        }
        while (totalChar['f'-'a'] > 0) {
            num[5]++;
            totalChar['f'-'a']--;
            totalChar['i'-'a']--;
            totalChar['v'-'a']--;
            totalChar['e'-'a']--;
        }
        while (totalChar['s'-'a'] > 0) {
            num[7]++;
            totalChar['s'-'a']--;
            totalChar['e'-'a']--;
            totalChar['v'-'a']--;
            totalChar['e'-'a']--;
            totalChar['n'-'a']--;
        }
        while (totalChar['n'-'a'] > 0) {
            num[9]++;
            totalChar['n'-'a']--;
            totalChar['i'-'a']--;
            totalChar['n'-'a']--;
            totalChar['e'-'a']--;
        }
        
        String res = "";
        StringBuffer sb = new StringBuffer(res);
        for (int i = 0; i < num.length; i++) {
            if (num[i] > 0) {
                for (int j = 0; j < num[i]; j++)
                    sb.append(String.valueOf(i));
            }
        }
        res = sb.toString();
        
        return res;
    }
}

回到目录


问题

Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.

You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.

Example:

Given 1->2->3->4->5->NULL,

return 1->3->5->2->4->NULL.

Note:

  1. The relative order inside both the even and odd groups should remain as it was in the input.
  2. The first node is considered odd, the second node even and so on ...

大意:

给出一个简单链表,集合所有奇数位置的节点,后面跟着所有偶数位置的节点。请注意这里我们说的是节点的位置而不是节点值。

你应该尝试在固定空间做。程序应该在O(1)的空间复杂度和O(nodes)的时间复杂度下运行。

例子:

给出 1->2->3->4->5->NULL,

返回 1->3->5->2->4->NULL。

注意:

  1. 偶数和奇数组中节点的相对位置要保持不变。
  2. 第一个节点被认为是奇数,第二个是偶数,如此往复。

思路:

题目的要求根据例子就可以明白,奇数和偶数位置的节点分成两段来排列,关键是要在O(1)的空间复杂度下做,否则直接用一个新链表就可以简单完成。

O(1)的空间下也好做,我们用两个头结点,一个为奇数的头结点,一个为偶数的头结点,然后遍历链表,奇数位置的节点就记录在奇数头结点后面,偶数位置的节点就记录在偶数头结点后面,两者是交替记录的,因为我们用的还是原来的节点,只是next指针指向的节点变了,所以并没有增加空间。遍历完后我们得到了奇偶两条链表,将偶链表的头结点接到奇链表的最尾端就可以了。

要注意一些节点为Null的处理。

代码(Java):

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) return head;
        
        ListNode even = head.next;
        ListNode evenNext = even;
        ListNode oddNext = head;
        while (evenNext != null) {
            oddNext.next = evenNext.next;
            
            if (oddNext.next != null) {
                oddNext = oddNext.next;
                evenNext.next = oddNext.next;
                evenNext = evenNext.next;
            } else {
                break;
            }
        }
        oddNext.next = even;
        return head;
    }
}

回到目录


问题:

There are n bulbs that are initially off. You first turn on all the bulbs. Then, you turn off every second bulb. On the third round, you toggle every third bulb (turning on if it's off or turning off if it's on). For the ith round, you toggle every i bulb. For the nth round, you only toggle the last bulb. Find how many bulbs are on after n rounds.

Example:

Given n = 3.

At first, the three bulbs are [off, off, off].

After first round, the three bulbs are [on, on, on].

After second round, the three bulbs are [on, off, on].

After third round, the three bulbs are [on, off, off].

So you should return 1, because there is only one bulb is on.

大意:

有n个灯泡初始是关着的。你首先把所有灯泡打开。然后每数到两个灯泡时关闭它。第三轮,将每数到第三个灯泡时改变它的状态(如果它是关着的就打开,如果是打开的就关闭)。第i轮,都将每数到第i个灯泡时切换状态。第n轮,你只用切换最后一个灯泡的状态。判断n轮后有多少灯泡开着。

例:

给出 n = 3。

首先,三个灯泡是 [关,关,关]。

第一轮后,三个灯泡是 [开,开,开]。

第二轮后,三个灯泡是 [开,关,开]。

第三轮后,三个灯泡是 [开,关,关]。

所以你应该返回 1,因为只有一个灯泡是开着的。

思路:

这个问题如果要用暴力方法去一轮轮遍历地做也能做,但是当遇到大数的时候会超时。

所以我们要找找规律。

其实第一轮的全开就是每数到一个灯泡时就转换状态。也就是说,对于每个灯泡而言,第i轮,只要i是它的约数,都会让它转换一次状态。初始状态是关,所以要想最后状态是开,那么就需要转换奇数次状态。

一个数的约数一般都是成对出现的,比如1和它本身,如果约数都是成对出现的,最后的转换次数就是偶数,一定是关着的状态。所以只有那些平方数,额外拥有一次单次的转换次数,比如4是22,9是33,这些数字在遇到2、3的时候都会额外单独转换一次,那最后就一定是关着的,所以我们的目的变成了找1到n中平方数的个数。

我们对n开平方根,会得到小于n的最大的一个平方数的平方根R,而1是最小的一个平方数,其实1-R之间的每个数,平方后都是小于n的,也就是说他们平方后的数都正好是最后状态为开着的灯泡序号。所以1-R之间有多少个数,就表示最后又多少个灯泡是开着的!

所以问题变得很简单,对n开平方根取整就行了!

代码:

public class Solution {
    public int bulbSwitch(int n) {
        return (int)Math.sqrt(n);
    }
}

回到目录


问题:

Given a collection of distinct numbers, return all possible permutations.

For example,

[1,2,3] have the following permutations:

[

[1,2,3],

[1,3,2],

[2,1,3],

[2,3,1],

 [3,1,2],

 [3,2,1]

]

大意:

给出一个不同数字的集合,返回所有可能的排列。

比如:

[1,2,3] 有下面这些排列:

[

[1,2,3],

[1,3,2],

[2,1,3],

[2,3,1],

[3,1,2],

[3,2,1]

]

思路:

这就是中学时排列组合的题目,要极尽所有排列,我们就要从第一个位置开始所有的元素都有可能放一遍,第二个位置在放了第一个位置的数字后剩下的数字又都可能放一遍,第三个位置也是类似,一直到最后一个位置。

对于这种操作用递归最合适,在我们创建的函数中对于第i个位置,每个当前还剩下的数字都去尝试放一次,并且继续往下递归,最后就会得到所有可能的排列。

要注意的一点就是在递归中我们的List和数组都要进行深拷贝然后添加元素,否则不同的放置方式操作的都是同一个List或者数组,没有意义。

代码(Java):

public class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> resList = new ArrayList<List<Integer>>();
        for (int i = 0; i < nums.length; i++) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(nums[i]);
            int[] hasUsed = new int[nums.length];
            hasUsed[i] = 1;
            doPermute(nums, resList, list, hasUsed);
        }
        return resList;
    }
    
    public void doPermute(int[] nums, List<List<Integer>> resList, List<Integer> list, int[] hasUsed) {
        if (list.size() == nums.length) {
            resList.add(list);
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (hasUsed[i] == 0) {
                List<Integer> newList = new ArrayList<Integer>();
                newList.addAll(list);
                newList.add(nums[i]);
                int[] newUsed = hasUsed.clone();
                newUsed[i] = 1;
                doPermute(nums, resList, newList, newUsed);
            }
        }
    }
}

回到目录


问题:

Given a string that consists of only uppercase English letters, you can replace any letter in the string with another letter at most k times. Find the length of a longest substring containing all repeating letters you can get after performing the above operations.

Note:

Both the string's length and k will not exceed 104.

Example 1:

Input:

s = "ABAB", k = 2

Output:

4

Explanation:

Replace the two 'A's with two 'B's or vice versa.

Example 2:

Input:

s = "AABABBA", k = 1

Output:

4

Explanation:

Replace the one 'A' in the middle with 'B' and form "AABBBBA".

The substring "BBBB" has the longest repeating letters, which is 4.

大意:

给出一个只由大写字母组成的字符串,你最多可以替换其中的字母k次。寻找执行替换操作后最长的相同字母的长度。

注意:

所有字符串的长度和k都不会超过10的4次方。

例1:

输入:

s = "ABAB", k = 2

输出:

4

解释:

用两个B替换两个A或者反过来。

例2:

输入:

s = "AABABBA", k = 1

输出:

4

解释:

用B替换中间的一个A得到“AABBBBA”。

子字符串“BBBB”有最长的重复字母的长度4。

思路:

题目的意思比较清楚,不过可能的情况有很多,不可能用代码去寻找最佳的替换位置,所以这里采用一种滑动窗口的方法。

定义start和end两个标记,中间的内容即是窗口,计算窗口内所有字母出现的次数,因为全是大写字母,所以可以用一个26位的数组来记录窗口内每个字母出现的次数。

找到窗口内出现最多的次数,加上允许替换的字母数k,看是否超过窗口宽度,如果超过了,说明窗口还可以更长, 也就是说窗口内重复的字母的长度可以更长,就将end右移一位,形成新的窗口,然后继续重复上面的步骤。如果没超过,说明能构成的最长的重复字母长度已经到顶了,这时应该将start右移一位,来寻找新的可能的更长重复字母长度。

每次计算重复字母长度时,当出现更长的可能时,都更新最终的结果。

为了减少时间复杂度,我们不去每次都遍历窗口来计算出现的字母次数,而是在移动end或者start时,将对应位置的字母的次数加一或者减一。

要注意各种边界条件是什么。

代码:

public class Solution {
    public int characterReplacement(String s, int k) {
        if (s.length() < 1) return 0;
        
        int start = 0;
        int end = 0;
        int res = 0;
        int[] charNum = new int[26];
        charNum[s.charAt(0) - 'A']++;
        while (s.length() > end) {
            int maxChar = 0;
            for (int i = 0; i < 26; i++) {
                if (charNum[i] > maxChar) maxChar = charNum[i];
            }
            
            if (maxChar + k > end - start) {
                end++;
                if (end < s.length()) charNum[s.charAt(end) - 'A']++;
            } else {
                charNum[s.charAt(start) - 'A']--;
                start++;
            }
            
            if (maxChar + k > res) res = maxChar + k <= s.length() ? maxChar + k : s.length();
        }
        return res;
    }
}

回到目录


问题:

Given an encoded string, return it's decoded string.

The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer.

You may assume that the input string is always valid; No extra white spaces, square brackets are well-formed, etc.

Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there won't be input like 3a or 2[4].

Examples:

s = "3[a]2[bc]", return "aaabcbc".

s = "3[a2[c]]", return "accaccacc".

s = "2[abc]3[cd]ef", return "abcabccdcdcdef".

大意:

给出一个编码了的字符串,返回它的解码字符串。

编码规则是:k[编码字符串],方括号里的编码字符串会重复k次。注意k保证是一个正整数。

你可以假设输入的字符串都是有效的;没有额外的空格,方括号是能够匹配的,等等。

此外,你可以假设原始数据不包含任何数字,数字只用来表示重复次数k。比如,不会有 3a 或者 2[4] 这样的输入。

例子:

s = "3[a]2[bc]", return "aaabcbc".

s = "3[a2[c]]", return "accaccacc".

s = "2[abc]3[cd]ef", return "abcabccdcdcdef".

思路:

这个思路比较直,遍历字符串,遇到数字后,记录下数字,获取方括号内的字符串,重复k次添加到结果字符串中。

需要注意的有两个地方,一个是方括号中可能嵌套重复的内容,所以一是要准确找到左括号对应的右括号是哪个,我们用一个变量来记录遇到的左括号和右括号数量就可以了,二是要用递归来操作内容的重复,因为里面还可能包含着重复的内容需要解码。另一个要注意的是数字不一定是一位数,还可能是多位数,因此当遇到数字后,要看看后面是不是还是数字,是的话要记录下来换算成真正的重复次数。

public class Solution {
    public String decodeString(String s) {
        return decodeSub(s, 1);
    }
    
    public String decodeSub(String sub, int count) {
        String trueStr = "";
        for (int i = 0; i < sub.length(); i++) {
            if (sub.charAt(i) - '1' >= 0 && sub.charAt(i) - '9' <= 0) {
                int subCount = sub.charAt(i) - '0';
                while (sub.charAt(i+1) - '0' >= 0 && sub.charAt(i+1) - '9' <= 0) {
                    i++;
                    subCount = subCount * 10 + sub.charAt(i) - '0';
                }
                
                int start = i+2;
                int num = 1;
                String subsub = "";
                for (i = i+2; i < sub.length(); i++) {
                    if (sub.charAt(i) - '[' == 0) num++;
                    else if (sub.charAt(i) - ']' == 0) num--;
                    
                    if (num == 0) {
                        subsub = sub.substring(start, i);
                        break;
                    }
                }
                trueStr = trueStr + decodeSub(subsub, subCount);
                
            } else {
                trueStr = trueStr + sub.substring(i, i+1);
            }
        }
        String decodeRes = "";
        for (int i = 0; i < count; i++) {
            decodeRes = decodeRes + trueStr;
        }
        return decodeRes;
    }
}

回到目录


问题:

Given a string and a string dictionary, find the longest string in the dictionary that can be formed by deleting some characters of the given string. If there are more than one possible results, return the longest word with the smallest lexicographical order. If there is no possible result, return the empty string.

Example 1:

Input:

s = "abpcplea", d = ["ale","apple","monkey","plea"]

Output:

"apple"

Example 2:

Input:

s = "abpcplea", d = ["a","b","c"]

Output:

"a"

Note:

  1. All the strings in the input will only contain lower-case letters.
  2. The size of the dictionary won't exceed 1,000.
  3. The length of all the strings in the input won't exceed 1,000.

大意:

给出一个字符串和字符串字典,找到字典中能够通过删除目标字符串一些字符来匹配到的最长的字符串。如果有多个结果,返回最长字符串中词典顺序最小的一个。如果没有结果,返回空字符串。

例1:

输入:

s = "abpcplea", d = ["ale","apple","monkey","plea"]

输出:

"apple"

例2:

输入:

s = "abpcplea", d = ["a","b","c"]

输出:

"a"

注意:

  1. 所有输入的字符串都只包含小写字母
  2. 字典的大小不会超过1000。
  3. 所有输入的字符串的长度不会超过1000。

思路:

遍历字典中的字符串,对每个字符串的每个字符按照顺序在目标字符串中找位置,为了保持顺序,每次找下一个字符的位置时都要从上一个找到的位置之后开始找,一旦某个字符找不到,就说明不匹配。

如果一个字符串能够匹配到,那么就看他的长度是多少,根据长度来更新记录的结果,如果长度一致,那就要根据两个字符串中的字符顺序来判断留哪个了。

代码:

public class Solution {
    public String findLongestWord(String s, List<String> d) {
        String res = "";
        int longest = 0;
        for (int i = 0; i < d.size(); i++) {
            String temp = d.get(i);
            int last = 0;
            boolean match = true;
            for (int j = 0; j < temp.length(); j++) {
                int index = s.indexOf(temp.substring(j, j+1), last) + 1;
                if (index == 0) match = false;
                else last = index;
            }
            if (match && temp.length() > longest) {
                longest = temp.length();
                res = temp;
            } else if (match && temp.length() == longest) {
                for (int j = 0; j < temp.length(); j++) {
                    if (temp.charAt(j) - res.charAt(j) < 0) {
                        res = temp;
                        break;
                    } else if (temp.charAt(j) - res.charAt(j) > 0) {
                        break;
                    }
                }
            }
        }
        return res;
    }
}

回到目录


问题:

Given an array of integers, every element appears three times except for one, which appears exactly once. Find that single one.

Note:

Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

大意:

给出整数数组,每个元素都出现三次,只有一个元素只出现一次,找到它。

注意:

你的算法应该是线性时间复杂度。能不用额外的空间来做吗?

思路:

用哈希表也可以做到线性时间复杂度,但是用了额外空间。

这里我们参考这个思路来做:传送门:算法题总结之找到数组中出现次数唯一不同的数字

代码(Java):

public class Solution {
    public int singleNumber(int[] nums) {
        int x1 = 0;
        int x2 = 0;
        int mask = 0;
        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            x2 ^= x1 & num;
            x1 ^= num;
            mask = ~(x1 & x2);
            x2 = x2 & mask;
            x1 = x1 & mask;
        }
        return x1;
    }
}

回到目录


问题:

Given an integer n, return 1 - n in lexicographical order.

For example, given 13, return: [1,10,11,12,13,2,3,4,5,6,7,8,9].

Please optimize your algorithm to use less time and space. The input size may be as large as 5,000,000.

思路:

给出一个整数n,返回 1 到 n 的词典顺序。

比如,给出13,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9]。

请优化你的代码来使用更少的时间和空间。输入范围会达到5000000。

思路:

问题的关键在于理清“词典顺序”是什么样的。

词典顺序就是10比2靠前,101比11靠前,110比11靠后,你可以简单的尝试一下把数字都转换成字符串数组,然后对字符串数组进行 Arrays.sort(),就会得到字符串下词典顺序的数字。但是这个方法因为用了排序,是会超时的。

所以我们的做法大致顺序如下:

1、如果一个数乘以十以后没有超过n,那它后面紧挨着的应该是它的十倍,比如1,10,100。 2、如果不满足1,那就应该是直接加一,比如n为13的时候,前一个数为12,120超过了n,那接着的应该是13。但是这里要注意如果前一个数的个位已经是9或者是它就是n了,那就不能加一了,比如 n = 25,前一个数为19,下一个数应该为2而不是19+1=20。25的下一个也没有26了。 3、如果不满足2,比如19后面应该接2而不是20,这时候应该将19除以10再加一,比如n=500,399的下一个应该是4,那就是除以十,个位还是9,继续除以10,得到3,加一得到4。

将上面的过程整理成代码就可以了,循环的次数就是n,也就是总个数。

代码(Java):

public class Solution {
    public List<Integer> lexicalOrder(int n) {
        List<Integer> res = new ArrayList<Integer>();
        int cur = 1;
        for (int i = 1; i <= n; i++) {
            res.add(cur);
            if (cur * 10 <= n) {
                cur = cur * 10;
            } else if (cur % 10 != 9 && cur + 1 <= n) {
                cur++;
            } else {
                while ((cur / 10) % 10 == 9) {
                    cur = cur / 10;
                }
                cur = cur / 10 + 1;
            }
        }
        return res;
    }
}

回到目录


问题:

Given n, how many structurally unique BST's (binary search trees) that store values 1...n?

For example,

Given n = 3, there are a total of 5 unique BST's.

大意:

给出n,包含1~n这些节点可以形成多少个不同的BST(二叉查找树)?

比如,

给出n = 3,有5个不同的BST:

思路:

二叉查找树的性质是左子节点一定小于父节点,右子节点一定大于父节点。

我们思考一下可以发现,要形成不同的二叉树,最基本的分类是1n各自都做一次根节点。在它们作为根节点时,又分别还有多少种不同的组合方式呢?由于这是一个二叉查找树,那么根节点的左边一定都是小于他的数,右边一定都是大于它的数,所以1n就会被分成两部分去放置,这时候由可以分别把左子节点、右子节点分别看成要安放一部分数字的根节点,又变成了一样的规律。

所以假设以i为根节点,可能的组合情况为F(i,n),而G(n)为输入n后的结果。则

F(i,n) = G(i-1)*G(n-i)

也就是左子节点以下的可能数量乘以右子节点以下的可能数量。

而因为1~n都可能作为根节点,所以最终的值是它们的和,也就是

G(n) = F(1,n) + F(2,n) + …… +F(n,n)

换算一下就是

G(n) = G(0) * G(n-1) + G(1) * G(n-2) + …… + G(n-1) *G(0)

其中我们可以直接看出 G(0) = G(1) = 1。这个作为初始值来递归计算就可以了,要知道G(n),我们必须把前面的数都计算出来。

代码(Java):

public class Solution {
    public int numTrees(int n) {
        int[] res = new int[n+1];
        res[0] = 1;
        res[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                res[i] += res[j-1] * res[i-j];
            }
        }
        return res[n];
    }
}

回到目录


问题:

Given a collection of intervals, find the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.

Note:

  1. You may assume the interval's end point is always bigger than its start point.
  2. Intervals like [1,2] and [2,3] have borders "touching" but they don't overlap each other.

Example 1:

Input: [ [1,2], [2,3], [3,4], [1,3] ]

Output: 1

Explanation: [1,3] can be removed and the rest of intervals are non-overlapping.

Example 2:

Input: [ [1,2], [1,2], [1,2] ]

Output: 2

Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.

Example 3:

Input: [ [1,2], [2,3] ]

Output: 0

Explanation: You don't need to remove any of the intervals since they're already non-overlapping.

大意:

给出一个间隔的集合,找到需要移除的间隔来保证剩余的间隔不发成范围重叠的最小间隔数量。

注意:

  1. 你可以假设间隔的end永远比start大。
  2. 类似[1,2]和[2,3]的间隔边界是重复的,但是不算做重叠。

例1:

输入:[ [1,2], [2,3], [3,4], [1,3] ]

输出:1

解释:[1,3]需要被移除就可以让剩余的无重叠。

例2:

输入:[ [1,2], [1,2], [1,2] ]

输出:2

解释:需要移除两个[1,2]来让剩余的无重叠。

例3:

输入:[ [1,2], [2,3] ]

输出:0

解释:不需要移除任何间隔,就已经是无重叠的了。

思路:

要移除尽可能少的间隔数,那么首先要知道有哪些部分是重叠了的,发生重叠后,就要尽量移除范围更大的,来保证更多范围小的可以留下。

为了知道有哪些是重叠的,我们先给所有的间隔排个序,这样就可以一个一个地看是否有重叠,间隔排序不像数字排序那么直接,需要自己进行定义,到底是比end的大小还是比start的大小呢?我们选择比end,如果比start,有可能一个start更小但范围很大的间隔,却包含了后面好几个start更大但是范围很小的间隔,而比end就没有这个问题,end基本就限制了你的范围。

那么比较end如果相同,还需要判断start吗?其实不需要了,对于同一个end值的两个间隔,无论我们留哪一个(只要能留,也就是说只要范围没重叠),另一个都一定会被抛弃,且对后续的间隔没有任何影响。

排序我们就可以遍历间隔数组,比较后一个间隔的start是否大于前一个的end,大于则无重叠,我们就留下来,否则就抛弃掉。最后得出抛弃掉的数量就可以了。

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
public class Solution {
    public int eraseOverlapIntervals(Interval[] intervals) {
        if (intervals.length == 0) return 0;
        Arrays.sort(intervals, (a, b) -> (a.end - b.end));
        int end = intervals[0].end;
        int count = 1;
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i].start >= end) {
                end = intervals[i].end;
                count++;
            }
        }
        return intervals.length-count;
    }
}

回到目录


问题:

The gray code is a binary numeral system where two successive values differ in only one bit.

Given a non-negative integer n representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0.

For example, given n = 2, return [0,1,3,2]. Its gray code sequence is:

00 - 0

01 - 1

11 - 3

10 - 2

Note:

  1. For a given n, a gray code sequence is not uniquely defined.
  2. For example, [0,1,3,2] is also a valid gray code sequence according to the above definition.
  3. For now, the judge is able to judge based on one instance of gray code sequence. Sorry about that.

大意:

格雷码是一种二进制编码系统,相邻的两个值之间只有一位是不同的。

给出一个非负数n,表示编码的总位数,输出格雷码序列。格雷码必须从0开始。

比如,给出n=2,返回[0,1,3,2]。它的格雷码序列是:

00 - 0

01 - 1

11 - 3

10 - 2

注意:

  1. 对于给出的n,格雷码序列并不是唯一的。
  2. 比如,[0,2,3,1]也是一种满足上面要求的序列。
  3. 现在,判断程序只支持一种格雷码序列,很抱歉。

思路:

格雷码是很经典的问题,规则其实很简单,在二进制形式下,任何响铃的两个值的二进制表示形式只有一位是不同的,我们可以找找规律。

一位就是简单的:0,1

两位是:00,01,11,10

三位是:000,001,011,010,110,111,101,100

发现什么规律没有?我们把一位的两个数,前面加上0,就是二位的头两个数,前面加上1再反序,就是二位的后两个数。把二位的前面加上0,就是三位的头四个数,把二位的前面加上1再反过来,就是三位的后四个数。

也就是说,对于每多一位的格雷码,前面一半的第一位都是0,后面一半的第一位都是1,其余的位,前后两半正好是中间对称的,前面一半就是少一位的格雷码序列,后面一半时把其反序。

知道这个规律就好做了,我们可以递归来做,每次取n-1位的格雷码来做上述操作,对于一位的格雷码,直接赋值是0,1就可以了。

不过题目要求返回的是十进制数,而不是字符串,所以我们最好直接操作十进制数,这里前面加0其实就不用做什么,前面加1的话可以将1左移n-1位然后与各个数字相加即可。

注意题目说的n是非负数,所以要考虑n=0的情况,测试用例的n=0时返回的是0。

代码(Java):

public class Solution {
    public List<Integer> grayCode(int n) {
        List<Integer> res = new ArrayList<Integer>();
        if (n == 0) {
            res.add(0);
            return res;
        } else if (n == 1) {
            res.add(0);
            res.add(1);
            return res;
        }
        
        List<Integer> last = grayCode(n-1);
        
        for (int i = 0; i < last.size()*2; i++) {
            if (i < last.size()) res.add(last.get(i));
            else {
                int temp = last.get(last.size()-(i-last.size())-1);
                temp += 1 << (n-1);
                res.add(temp);
            }
        }
        
        return res;
    }
}

回到目录


问题:

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:

  • You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
  • After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

Example:

prices = [1, 2, 3, 0, 2]

maxProfit = 3

transactions = [buy, sell, cooldown, buy, sell]

大意:

你有一个数组,第i个元素表示股票第i天的金额。

设计一个算法来找到最大收益。你可以随意交易多次(比如多次买入卖出),但要遵循下面的规则:

  • 你不能同时做多次操作(也就是卖出之前必须买入)。
  • 你卖出股票后,接下来那一天不能买股票。(需要冷却一天)

例子:

prices = [1, 2, 3, 0, 2]

maxProfit = 3

交易 = [买入, 卖出, 冷却, 买入, 卖出]

思路:

这是一个典型的需要动态规划来做的题,有三种操作类型:买入(buy)、卖出(sell)、休息(rest)。根据动态规划的方法,我们需要列出三种操作的计算方程,三个方程分别表示,以这三种操作作为最后一次操作的情况下的最终最大收益。

根据定义,我们来一个个看:

买入的前一天必须是休息,最后一天如果要是买入,那最后还需要消耗一笔金钱,这笔金钱是当日的股票价price,那么方程是:

buy[i] = max{rest[i-1]-price, buy[i-1]}

卖出的前一天必须是买入,最后一天卖出后,会多得到当日的股价price,那么方程是:

sell[i] = max{buy[i-1]+price, sell[i-1]}

休息的话有多种情况,最后一天休息的话,前一天可以是买入、卖出或者休息,且最后一天也不会有进账或者消费,那么方程是:

rest[i] = max{buy[i-1], sell[i-1], rest[i-1]}

但是稍微想一下就知道,最后一天买入后的收益一定小于最后一天休息的收益,由小于最后一天卖出的收益,即:

buy[i] <= rest[i] <= sell[i]

那么我们rest的方程就可以变为:

rest[i] = sell[i-1]

代入buy和sell的方程就是:

buy[i] = max{sell[i-2]-price, buy[i-1]}

sell[i] = max{buy[i-1]+price, sell[i-1]}

由于每次计算第i个值时我们只用到了最多前两个sell和前一个buy,所以我们不需要记录整个数组的buy和sell,将空间复杂度降低到O(1),只需要记录前两个sell和前一个buy,根据代码的写法,我们甚至只需要记录前一个sell,将对sell的计算放在buy之后,那么自然而然就变成前两个sell了。

代码(Java):

public class Solution {
    public int maxProfit(int[] prices) {
        int sell = 0, prev_sell = 0, buy = Integer.MIN_VALUE, prev_buy;
        for (int price : prices) {
            prev_buy = buy;
            buy = Math.max(prev_sell - price, prev_buy);
            prev_sell = sell;
            sell = Math.max(prev_buy + price, prev_sell);
        }
        return sell;
    }
}

回到目录


不断更新中...

更多内容查看我的博客

About

LeetCode笔记