Java解leetcode,助力面试之中等10道题(三)
第91题 解码方法
一条包含字母 A-Z 的消息通过以下映射进行了 编码 : ‘A’ -> 1 ‘B’ -> 2 … ‘Z’ -> 26 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为: “AAJF” ,将消息分组为 (1 1 10 6) “KJF” ,将消息分组为 (11 10 6) 注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。 给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。 题目数据保证答案肯定是一个 32 位 的整数。
示例 1:
解释:它可以解码为 “AB”(1 2)或者 “L”(12)。
示例 2:
解释:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
示例 3:
解释:没有字符映射到以 0 开头的数字。 含有 0 的有效映射是 ‘J’ -> “10” 和 ‘T’-> “20” 。 由于没有字符,因此没有有效的方法对此进行解码,因为所有数字都需要映射。
解题思路
本题使用动态规划求解,每一个位置处的解码方法有2种情况,一是单个数字组成一个字母,二是;两个数字组成一个字母,所以当前位置的解码方法取决去前两个位置的解码方法之和,因此f[i]=f[i-1]+f[i-2],只要满足数字在1-26之间,就可认为是一个字母。
代码
class Solution {
public int numDecodings(String s) {
int n = s.length();
int a = 0, b = 1, c = 0;
for (int i = 1; i <= n; ++i) {
c = 0;
if (s.charAt(i - 1) != '0') {
c += b;
}
if (i > 1 && s.charAt(i - 2) != '0' && ((s.charAt(i - 2) - '0') * 10 + (s.charAt(i - 1) - '0') <= 26)) {
c += a;
}
a = b;
b = c;
}
return c;
}
}
时间复杂度为O(n),n为字符串长度 空间复杂度为O(1)
第93题 复原 IP 地址
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。 例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
示例 1:
输入 | 输出 |
---|
s = “25525511135” | [“255.255.11.135”,“255.255.111.35”] |
示例 2:
输入 | 输出 |
---|
s = “0000” | [“0.0.0.0”] |
示例 3:
输入 | 输出 |
---|
s = “010010” | [“0.10.0.10”,“0.100.1.0”] |
示例 4:
输入 | 输出 |
---|
s = “1111” | [“1.1.1.1”] |
示例 5:
输入 | 输出 |
---|
s = “101023” | [“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”] |
解题思路
用回溯法,如果找到了4段IP地址且遍历完了数组,则找到了一个答案,如果找到4段IP地址,但是还没有遍历完数组,则重新找,当没有找到4段时,如果当前遍历到了单个0,则将0单独作为一段,否则判断当前数字是否超出范围,没有则加入一段。
代码
class Solution {
static final int SEG_COUNT = 4;
List<String> ans = new ArrayList<String>();
int[] segments = new int[SEG_COUNT];
public List<String> restoreIpAddresses(String s) {
segments = new int[SEG_COUNT];
dfs(s, 0, 0);
return ans;
}
public void dfs(String s, int segId, int segStart) {
if (segId == SEG_COUNT) {
if (segStart == s.length()) {
StringBuffer ipAddr = new StringBuffer();
for (int i = 0; i < SEG_COUNT; ++i) {
ipAddr.append(segments[i]);
if (i != SEG_COUNT - 1) {
ipAddr.append('.');
}
}
ans.add(ipAddr.toString());
}
return;
}
if (segStart == s.length()) {
return;
}
if (s.charAt(segStart) == '0') {
segments[segId] = 0;
dfs(s, segId + 1, segStart + 1);
}
int addr = 0;
for (int segEnd = segStart; segEnd < s.length(); ++segEnd) {
addr = addr * 10 + (s.charAt(segEnd) - '0');
if (addr > 0 && addr <= 0xFF) {
segments[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
} else {
break;
}
}
}
}
时间复杂度为O(3^SEG_COUNT×∣s∣),s为字符串长度,SEG_COUNT为4 空间复杂度为O(SEG_COUNT)
第95题 不同的二叉搜索树 II
给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案
示例 1:
输入 | 输出 |
---|
n = 3 | [[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]] |
示例 2:
解题思路
先找出全是左子树的一个集合以及全是右子树的一个集合,然后从左子树和右子树各选出一个子树拼接成根节点。
代码
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n == 0) {
return new LinkedList<TreeNode>();
}
return generateTrees(1, n);
}
public List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> allTrees = new LinkedList<TreeNode>();
if (start > end) {
allTrees.add(null);
return allTrees;
}
for (int i = start; i <= end; i++) {
List<TreeNode> leftTrees = generateTrees(start, i - 1);
List<TreeNode> rightTrees = generateTrees(i + 1, end);
for (TreeNode left : leftTrees) {
for (TreeNode right : rightTrees) {
TreeNode currTree = new TreeNode(i);
currTree.left = left;
currTree.right = right;
allTrees.add(currTree);
}
}
}
return allTrees;
}
}
时间复杂度为O(4n/n(1/2)),n为数字大小 空间复杂度为O(4n/n(1/2))
第102题 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入 | 输出 |
---|
[3,9,20,null,null,15,7] | [ [3], [9,20], [15,7]] |
解题思路
使用BFS,即队列,先将根节点入队,然后将根节点出队,加入当前层级,当前层级大小等于当前队列的长度,每次出队一个节点,都将该节点值加入当前层级的结果中,然后将它的左右子节点加入队列,直到遍历完所有节点
代码
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
if (root == null) {
return ret;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<Integer>();
int currentLevelSize = queue.size();
for (int i = 1; i <= currentLevelSize; ++i) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
ret.add(level);
}
return ret;
}
}
时间复杂度为O(n),n为节点数 空间复杂度为O(n)
第107题 二叉树的层序遍历 II
给定一个二叉树,返回其节点值自底向上的层序遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
示例 1:
输入 | 输出 |
---|
[3,9,20,null,null,15,7] | [ [15,7], [9,20], [3]] |
解题思路
本题和102题类似,只是在将每层的结果加入数组中时,有所不同,102题是加入数组后面,而本题是加入到数组前面。
代码
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
if (root == null) {
return levelOrder;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<Integer>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
TreeNode left = node.left, right = node.right;
if (left != null) {
queue.offer(left);
}
if (right != null) {
queue.offer(right);
}
}
levelOrder.add(0, level);
}
return levelOrder;
}
}
时间复杂度为O(ln),n为节点数 空间复杂度为O(n)
第109题 有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例 1:
输入 | 输出 |
---|
[-10, -3, 0, 5, 9] | [0, -3, 9, -10, null, 5] |
解题思路
采用分治算法,每次将长度分成左右两部分,将中间数做为根节点,不断找新的左右子树。
代码
class Solution {
ListNode globalHead;
public TreeNode sortedListToBST(ListNode head) {
globalHead = head;
int length = getLength(head);
return buildTree(0, length - 1);
}
public int getLength(ListNode head) {
int ret = 0;
while (head != null) {
++ret;
head = head.next;
}
return ret;
}
public TreeNode buildTree(int left, int right) {
if (left > right) {
return null;
}
int mid = (left + right + 1) / 2;
TreeNode root = new TreeNode();
root.left = buildTree(left, mid - 1);
root.val = globalHead.val;
globalHead = globalHead.next;
root.right = buildTree(mid + 1, right);
return root;
}
}
时间复杂度为O(n),n为链表长度 空间复杂度为O(log n)
第128题 最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
示例 1:
输入 | 输出 |
---|
nums = [100,4,200,1,3,2] | 4 |
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入 | 输出 |
---|
nums = [0,3,7,2,5,8,4,6,0,1] | 9 |
解题思路
代码
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> num_set = new HashSet<Integer>();
for (int num : nums) {
num_set.add(num);
}
int longestStreak = 0;
for (int num : num_set) {
if (!num_set.contains(num - 1)) {
int currentNum = num;
int currentStreak = 1;
while (num_set.contains(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
}
}
时间复杂度为O(n),n为数组的长度 空间复杂度为O(n)
第130题 被围绕的区域
给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例 1:
输入 | 输出 |
---|
board = [[“X”,“X”,“X”,“X”],[“X”,“O”,“O”,“X”],[“X”,“X”,“O”,“X”],[“X”,“O”,“X”,“X”]] | [[“X”,“X”,“X”,“X”],[“X”,“X”,“X”,“X”],[“X”,“X”,“X”,“X”],[“X”,“O”,“X”,“X”]] |
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
解题思路
本题用DFS搜索四条边上是否有O,有则标记为A,然后在标记为A的四周找是否有O的,有则也标记为A,然后重新遍历整个二维数组,如果碰到O,则标记为X,遇到A则标记为O。
代码
class Solution {
int n, m;
public void solve(char[][] board) {
n = board.length;
if (n == 0) {
return;
}
m = board[0].length;
for (int i = 0; i < n; i++) {
dfs(board, i, 0);
dfs(board, i, m - 1);
}
for (int i = 1; i < m - 1; i++) {
dfs(board, 0, i);
dfs(board, n - 1, i);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
public void dfs(char[][] board, int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') {
return;
}
board[x][y] = 'A';
dfs(board, x + 1, y);
dfs(board, x - 1, y);
dfs(board, x, y + 1);
dfs(board, x, y - 1);
}
}
时间复杂度为O(mn),m,n为数组的行和列 空间复杂度为O(mn)
第131题 分割回文串
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。
示例 1:
输入 | 输出 |
---|
s = “aab” | [[“a”,“a”,“b”],[“aa”,“b”]] |
示例 2:
解题思路
利用回溯算法,不断查找是否为回文串,然后将子串加入ans中,直到遍历完字符串,如果都满足,则将ans加入到ret中,然后继续从头遍历,中间加入了剪枝操作,避免出现相同结果。
代码
class Solution {
boolean[][] f;
List<List<String>> ret = new ArrayList<List<String>>();
List<String> ans = new ArrayList<String>();
int n;
public List<List<String>> partition(String s) {
n = s.length();
f = new boolean[n][n];
for (int i = 0; i < n; ++i) {
Arrays.fill(f[i], true);
}
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];
}
}
dfs(s, 0);
return ret;
}
public void dfs(String s, int i) {
if (i == n) {
ret.add(new ArrayList<String>(ans));
return;
}
for (int j = i; j < n; ++j) {
if (f[i][j]) {
ans.add(s.substring(i, j + 1));
dfs(s, j + 1);
ans.remove(ans.size() - 1);
}
}
}
}
时间复杂度为O(n·2^n),n为字符串长度 空间复杂度为O(n^2)
第139题 单词拆分
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。
示例 1:
输入 | 输出 |
---|
s = “leetcode”, wordDict = [“leet”, “code”] | true |
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:
输入 | 输出 |
---|
s = “applepenapple”, wordDict = [“apple”, “pen”] | true |
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。 注意你可以重复使用字典中的单词。
示例 3:
输入 | 输出 |
---|
s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”] | false |
解题思路
一段一段判断是否在字符串中出现
代码
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
时间复杂度为O(n^2),n为字符串长度 空间复杂度为O(n)
|