好長時間沒有寫博客了,之前因爲期末考試耽誤了一段時間,回家又玩了幾天,然後又趕來上海入職,所以就把博客這事給忘了,哈哈,懶惰啊。
一、最長迴文字符串
題目:
/** * 給定一個包含大寫字母和小寫字母的字符串,找到通過這些字母構造成的最長的迴文串。 * 在構造過程中,請注意區分大小寫。比如 "Aa" 不能當做一個迴文字符串。 * <p> * 注意: * 假設字符串的長度不會超過 1010。 * <p> * 示例 1: * 輸入: * "abccccdd" * 輸出: * 7 * 解釋: * 我們可以構造的最長的迴文串是"dccaccd", 它的長度是 7。 */
這道題的意思很簡單,給你一些字母,問你最長可以組成多長的迴文字符串。那就想一下回文字符串的判定條件唄,就是中心對稱,那就是說中間可以有一個不一樣的字符,兩邊的字符出現的次數是偶數的,那就這樣計算吧。
大體的思想就是先對每個字符出現的次數進行計數然後對偶數加和,只加一個奇數,其它奇數減一變偶數加和
public static int method1(String s) {
int[] a = new int[256];
for (char c : s.toCharArray()) {
a[c]++;
}
int res = 0;
boolean flag = true;
for (int i = 0; i < 256; i++) {
if (a[i] % 2 == 0) {
res += a[i];
} else {
if (flag) {
res += a[i];
flag = false;
} else {
res += a[i] - 1;
}
}
}
return res;
}
二、第三大的數
題目:
/** * 給定一個非空數組,返回此數組中第三大的數。如果不存在,則返回數組中最大的數。要求算法時間複雜度必須是O(n)。 * * 示例 1: * 輸入: [3, 2, 1] * 輸出: 1 * 解釋: 第三大的數是 1. * * 示例 2: * 輸入: [1, 2] * 輸出: 2 * 解釋: 第三大的數不存在, 所以返回最大的數 2 . * * 示例 3: * 輸入: [2, 2, 3, 1] * 輸出: 1 * * 解釋: 注意,要求返回第三大的數,是指第三大且唯一出現的數。 * 存在兩個值爲2的數,它們都排第二。 */
先給出一種不符合要求的解法,先排序在去重前三大的數,如果去重後的數少於三個就返回最大的數,否則返回第三大的數
public static int method1(int[] nums) {
if (nums.length == 1) {
return 0;
}
Arrays.sort(nums);
int[] results = new int[3];
int num = 0;
for (int i = nums.length - 1; i >= 0; i--) {
if (num < 3 && nums[i] != results[num - 1 < 0 ? 0 : num - 1]) {
results[num++] = nums[i];
}
}
if (num < 3) {
return results[0];
} else {
return results[2];
}
}
很明顯它的複雜度是超過了O(n)的,sort()的複雜度是O(nlgn)
其實這道題也不難想,就是記錄前三大的數都是多少,然後遇到新的數就不斷傳遞就好了,再開始要判斷一下數組長度,還有就是在傳遞數字的時候記錄一下改變了多少次,以此來判斷有沒有第三大的數。
public static int method2(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
if (nums.length == 2) {
return Math.max(nums[0], nums[1]);
}
int max1 = Integer.MIN_VALUE;
int max2 = Integer.MIN_VALUE;
int max3 = Integer.MIN_VALUE;
boolean f = true;
int flag = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == Integer.MIN_VALUE && f) {
flag++;
f = false;
}
if (nums[i] > max1) {
flag++;
//原先第二大傳遞給第三大
max3 = max2;
//原先最大值傳遞給第二大
max2 = max1;
//更新最大值
max1 = nums[i];
} else if (nums[i] > max2 && nums[i] < max1) {
flag++;
max3 = max2;
max2 = nums[i];
} else if (nums[i] > max3 && nums[i] < max2) {
flag++;
max3 = nums[i];
}
}
return flag >= 3 ? max3 : max1;
}
還是相同的思想,我們來簡化一下代碼,不使用flag來計數判斷是否有第三大的數了,而是在最後判斷表示第三大的變量是否改變過,這樣判斷第三大的數是否出現過。
public static int method3(int[] nums) {
long first = Long.MIN_VALUE, second = Long.MIN_VALUE, third = Long.MIN_VALUE;
for (long num : nums) {
if (num > first) {
third = second;
second = first;
first = num;
} else if (num > second && num < first) {
third = second;
second = num;
} else if (num > third && num < second) {
third = num;
}
}
return third == Long.MIN_VALUE ? (int) first : (int) third;
}
三、路徑數量
題目:
/** * 給定一個二叉樹,它的每個結點都存放着一個整數值。 * 找出路徑和等於給定數值的路徑總數。 * 路徑不需要從根節點開始,也不需要在葉子節點結束,但是路徑方向必須是向下的(只能從父節點到子節點)。 * 二叉樹不超過1000個節點,且節點數值範圍是 [-1000000,1000000] 的整數。 * * 示例: * root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 * * 10 * / \ * 5 -3 * / \ \ * 3 2 11 * / \ \ * 3 -2 1 * * 返回 3。和等於 8 的路徑有: * * 1. 5 -> 3 * 2. 5 -> 2 -> 1 * 3. -3 -> 11 */ public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } }
一看這道題就感覺要用遞歸解,其實很多樹的題用遞歸來解都非常合適,有一個比較簡單的思路來解這道題,就是以每一個節點作爲根節點向下尋找是否有符合條件的路徑,有的話就把路徑數加一,可以用目標數值減去每一個節點,如果可以得到零的話就說明這是一條符合條件的路徑,因爲可能出現負值,所以不必對下於零的情況做處理。
private static int pathnumber;
public int method1(TreeNode root, int sum) {
if(root == null) return 0;
Sum(root,sum);
method1(root.left,sum);
method1(root.right,sum);
return pathnumber;
}
public static void Sum(TreeNode root, int sum){
if(root == null) return;
sum-=root.val;
if(sum == 0){
pathnumber++;
}
Sum(root.left,sum);
Sum(root.right,sum);
}
簡化一下代碼就是這個樣子,把計數也合到遞歸裏面
public int method2(TreeNode root, int sum) {
if (root == null) return 0;
return method2(root.left, sum) + method2(root.right, sum) + dfs(root, sum);
}
public static int dfs(TreeNode node, int sum) {
if (node == null) return 0;
int count = 0;
if (node.val == sum) count = 1;
return count + dfs(node.left, sum - node.val) + dfs(node.right, sum - node.val);
}
換湯不換藥,可以使用Map來記錄每個數值出現的次數,如果加和等於目標數值了便計數,比較重要的有兩句話,開始的,<0,1>鍵值對和最後的回溯。
public static int method3(TreeNode root, int sum) {
HashMap<Integer,Integer> map = new HashMap();
//<和,次數>
map.put(0, 1);
return FindSubTree(root,0,sum,map);
}
public static int FindSubTree(TreeNode root,int sum,int target,Map<Integer,Integer> map) {
if(root==null) {
return 0;
}
sum +=root.val;
int res = map.getOrDefault(sum-target, 0);
map.put(sum, map.getOrDefault(sum, 0)+1);
res +=FindSubTree(root.left,sum,target,map);
res +=FindSubTree(root.right,sum,target,map);
//回溯
map.put(sum, map.get(sum)-1);
return res;
}
四、N叉樹的層次遍歷
題目:
/** * 給定一個 N 叉樹,返回其節點值的層序遍歷。 (即從左到右,逐層遍歷)。 * * 例如,給定一個 3叉樹 : * LevelOrder.java * * 返回其層序遍歷: * * [ * [1], * [3,2,4], * [5,6] * ] * * 說明: * 樹的深度不會超過 1000。 * 樹的節點總數不會超過 5000。 * */ class Node { public int val; public List<Node> children; public Node() {} public Node(int _val,List<Node> _children) { val = _val; children = _children; } };
其實很簡單,和樹的層次遍歷一樣,可以使用兩個隊列,先把根節點放到一個隊列裏,然後取出來的時候加入一個list,再把子節點放到另一個隊列裏,取出來的時候加入一個list裏面,這樣不斷在兩個隊列裏面搗騰,直到兩個隊列都爲空。
public List<List<Integer>> method1(Node root) {
Queue<Node> nodeQueueA=new LinkedBlockingQueue<>();
Queue<Node> nodeQueueB=new LinkedBlockingQueue<>();
List<List<Integer>> lists=new ArrayList<>();
if (root!=null){
nodeQueueA.offer(root);
}
while (!nodeQueueA.isEmpty()||!nodeQueueB.isEmpty()){
List<Integer> integers=new ArrayList<>();
while (!nodeQueueA.isEmpty()){
Node node=nodeQueueA.poll();
integers.add(node.val);
for (Node node1:node.children){
nodeQueueB.offer(node1);
}
}
if (!integers.isEmpty()){
lists.add(integers);
integers=new ArrayList<>();
}
while (!nodeQueueB.isEmpty()){
Node node=nodeQueueB.poll();
integers.add(node.val);
for (Node node1:node.children){
nodeQueueA.offer(node1);
}
}
if (!integers.isEmpty()) {
lists.add(integers);
}
}
return lists;
}
也可以使用一個隊列,這樣你就需要控制每一次取出來多少個了,用以實現分層,每次要把當前隊列中節點的所有子節點放入一個list,並把所有子節點放入隊列。
public List<List<Integer>> method2(Node root) {
Queue<Node> nodeQueue=new LinkedBlockingQueue<>();
List<List<Integer>> lists=new ArrayList<>();
if (root!=null){
nodeQueue.offer(root);
List<Integer> integers=new ArrayList<>();
integers.add(root.val);
lists.add(integers);
}
while (!nodeQueue.isEmpty()){
List<Integer> integers=new ArrayList<>();
int size=nodeQueue.size();
for (int i=0;i<size;i++){
Node node=nodeQueue.poll();
for (Node node1:node.children){
nodeQueue.offer(node1);
integers.add(node1.val);
}
}
if (!integers.isEmpty()){
lists.add(integers);
}
}
return lists;
}
簡化版本
public List<List<Integer>> method3(Node root) {
List<List<Integer>> result = new LinkedList<>();
if (root == null) {
return result;
}
Node head = root;
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
int size = queue.size();
List<Node> nextLayer = new ArrayList<>();
List<Integer> layer = new ArrayList<>();
for (int i = 0; i < size; i++) {
Node node = queue.poll();
nextLayer.addAll(node.children);
layer.add(node.val);
}
result.add(layer);
queue.addAll(nextLayer);
}
return result;
}
還有一種辦法就是用dfs,遞歸的時候帶上層數,這樣就知道加到哪個list裏面了
private static List<List<Integer>> list = new ArrayList<>();
public static List<List<Integer>> method4(Node root) {
dfs(root, 0);
return list;
}
private static void dfs(Node root, int level){
if(root == null){
return;
}
if(list.size()<level+1){
List<Integer> l = new ArrayList<>();
list.add(l);
}
list.get(level).add(root.val);
for (Node node : root.children){
dfs(node, level+1);
}
}
五、字母異位詞
題目:
/** * 給定一個字符串 s 和一個非空字符串 p,找到 s 中所有是 p 的字母異位詞的子串,返回這些子串的起始索引。 * 字符串只包含小寫英文字母,並且字符串 s 和 p 的長度都不超過 20100。 * 說明: * 字母異位詞指字母相同,但排列不同的字符串。 * 不考慮答案輸出的順序。 * * 示例 1: * 輸入: * s: "cbaebabacd" p: "abc" * 輸出: * [0, 6] * 解釋: * 起始索引等於 0 的子串是 "cba", 它是 "abc" 的字母異位詞。 * 起始索引等於 6 的子串是 "bac", 它是 "abc" 的字母異位詞。 * * 示例 2: * 輸入: * s: "abab" p: "ab" * 輸出: * [0, 1, 2] * 解釋: * 起始索引等於 0 的子串是 "ab", 它是 "ab" 的字母異位詞。 * 起始索引等於 1 的子串是 "ba", 它是 "ab" 的字母異位詞。 * 起始索引等於 2 的子串是 "ab", 它是 "ab" 的字母異位詞。 */
字母異位詞很好判斷,只要裏面所包含的字母出現的次數一致就叫字母異位詞,一個很明顯的思路就是存儲s子串的字母構成,如果和p一樣就記錄其實索引,往後不斷拋棄s子串尾的字母,加入新的字母進行比對。
public static List<Integer> method2(String s, String p) {
List<Integer> result = new ArrayList<>();
int[] p_letter = new int[26];
for (int i = 0; i < p.length(); i++) {
//記錄p裏面的數字分別有幾個
p_letter[p.charAt(i) - 'a']++;
}
int start = 0; int end = 0;
int[] between_letter = new int[26];
//記錄兩個指針之間的數字都有幾個
while (end < s.length()) {
int c = s.charAt(end++) - 'a';
//每一次拿到end指針對應的字母
between_letter[c]++;
//讓這個字母的數量+1 如果這個字母的數量比p裏面多了,說明這個start座標需要排除
while (between_letter[c] > p_letter[c]) {
between_letter[s.charAt(start++) - 'a']--;
}
if (end - start == p.length()) {
result.add(start);
}
}
return result;
}
public static List<Integer> method3(String s, String p) {
List<Integer> list = new ArrayList<>();
int[] record = new int[128];
for(char c: p.toCharArray()){
record[c]++;
}
char[] arr = s.toCharArray();
int l=0, r=0;
while(r<s.length()){
if(record[arr[r]]>0){
record[arr[r]]--;
r++;
if((r-l) == p.length()){
list.add(l);
}
}
else{
record[arr[l]]++;
l++;
}
}
return list;
}