需要开通vip的题目暂时跳过
点击链接可跳转到所有刷题笔记的导航链接
给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。
求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。
说明: 请尽可能地优化你算法的时间和空间复杂度。
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int[] res = new int[k];
for (int i = 0; i <= k; i++) {
int j = k - i;
if (i > nums1.length || j > nums2.length) continue;
int[] max1 = findMax(nums1, nums1.length - i);
int[] max2 = findMax(nums2, nums2.length - j);
int[] temp = merge(max1, max2);
res = compare(res, 0, temp, 0) ? res : temp;
}
return res;
}
public int[] findMax(int[] nums, int k) {
//删除k个使得数字最大
if (k == nums.length) return new int[0];
int number = 0;
Stack<Integer> stack = new Stack<>();
stack.add(nums[0]);
for (int i = 1; i < nums.length; i++) {
while (!stack.isEmpty() && number < k) {
int top = stack.peek();
if (nums[i] > top) {
stack.pop();
number++;
} else break;
}
stack.add(nums[i]);
}
while(number < k){
stack.pop();
number++;
}
int[] res = new int[stack.size()];
int index = res.length - 1;
while (!stack.isEmpty())
res[index--] = stack.pop();
return res;
}
public int[] merge(int[] nums1, int[] nums2) {
//合并
int[] res = new int[nums1.length + nums2.length];
int index1 = 0;
int index2 = 0;
int index = 0;
while ((index1 < nums1.length || index2 < nums2.length) && index < res.length) {
if (index1 == nums1.length) {
while (index2 < nums2.length && index < res.length) res[index++] = nums2[index2++];
} else if (index2 == nums2.length) {
while (index1 < nums1.length && index < res.length) res[index++] = nums1[index1++];
} else {
if (nums1[index1] < nums2[index2]) res[index++] = nums2[index2++];
else if (nums1[index1] > nums2[index2]) res[index++] = nums1[index1++];
else {
if (compare(nums1, index1, nums2, index2)) res[index++] = nums1[index1++];
else res[index++] = nums2[index2++];
}
}
}
return res;
}
public boolean compare(int[] nums1, int index1, int[] nums2, int index2) {
//比较
while (index1 < nums1.length && index2 < nums2.length) {
if (nums1[index1] > nums2[index2]) return true;
else if (nums1[index1] < nums2[index2]) return false;
index1++;
index2++;
}
if (index1 < nums1.length) return true;
else return false;
}
分析
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
解答
int res3 = Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
dfs(coins, amount, 0, coins.length - 1);
return res3 == Integer.MAX_VALUE ? -1 : res3;
}
public void dfs(int[] coins, int amount, int number, int index) {
if (amount == 0) {
res3 = Math.min(res3, number);
return;
}
if (index < 0) return;
int n = amount / coins[index];
for (int i = n; i >= 0 && number + i < res3; i--) {
dfs(coins, amount - coins[index] * i, number + i, index - 1);
}
}
分析
给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。
int n=-1;
public void wiggleSort(int[] nums) {
//找到中位数索引
int midIndex = this.quickSelect(nums,0,nums.length-1);
//找到中位数
int mid = nums[midIndex];
n=nums.length;
//三分法
for(int i=0,j=0,k=nums.length-1;j<=k;){
if(nums[V(j)]>mid){
swap(nums,V(j++),V(i++));
}else if(nums[V(j)]<mid){
swap(nums,V(j),V(k--));
}else{
j++;
}
}
}
public int V(int i){
return (1+2*(i)) % (n|1);
}
public void swap(int[] nums,int i,int j){
int t = nums[i];
nums[i]=nums[j];
nums[j]=t;
}
public int quickSelect(int[] nums,int left,int right){
int pivot = nums[left];
int l = left;
int r = right;
while(l<r){
while(l<r&&nums[r]>=pivot){
r--;
}
if(l<r){
nums[l++]=nums[r];
}
while(l<r&&nums[l]<=pivot){
l++;
}
if(l<r){
nums[r--]=nums[l];
}
}
nums[l]=pivot;
if(l==nums.length/2){
return l;
}else if(l>nums.length/2){
return this.quickSelect(nums,left,l-1);
}else{
return this.quickSelect(nums,l+1,right);
}
}
分析
给定一个整数,写一个函数来判断它是否是 3 的幂次方。
//方法一
public boolean isPowerOfThree(int n) {
return Integer.toString(n, 3).matches("^10*$");
}
//方法二
public boolean isPowerOfThree(int n) {
return n > 0 && 1162261467 % n == 0;
}
分析
提交结果
给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
解答
public int countRangeSum(int[] nums, int lower, int upper) {
if(nums == null || nums.length == 0){
return 0;
}
//键值为区间和和这个区间和出现的次数
TreeMap<Long, Integer> tree = new TreeMap<>();
tree.put(0L, 1);
int count = 0;
long sum = 0L;
for(int num : nums){
sum += num;
//subMap()返回一个值在sum - upper 和sum - lower 之间的子集合,values()方法获得这个映射的值得视图
for(int cnt : tree.subMap(sum - upper, true, sum - lower, true).values()){
count += cnt; //统计满足条件的区间和个数
}
tree.put(sum, tree.getOrDefault(sum, 0) + 1);
}
return count;
}
分析
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next == null)return head;
ListNode odd = head;
ListNode even = head.next;
ListNode evenFirst = even;
while(even.next !=null){
odd.next = even.next;
odd = even.next;
if(odd.next!=null){
even.next = odd.next;
even = odd.next;
}else even.next = null;
}
odd.next = evenFirst;
return head;
}
分析
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
int[][] points = {
{
1,0},{
-1,0},{
0,1},{
0,-1}};
public int longestIncreasingPath(int[][] matrix) {
if(matrix == null || matrix.length==0 || matrix[0].length==0)return 0;
int row = matrix.length;
int column = matrix[0].length;
int[][] dp = new int[row][column];
int max = 0;
for(int i = 0;i<row;i++){
for(int j = 0;j<column;j++){
max = Math.max(max,dfs(matrix,i,j,dp));
}
}
return max;
}
public int dfs(int[][] matrix,int row,int column,int[][] dp){
if(dp[row][column] != 0)
return dp[row][column];
++dp[row][column];
for(int[] point:points){
int newRow = row+point[0];
int newColumn = column+point[1];
if(newRow>=0 && newRow < matrix.length && newColumn >= 0 && newColumn < matrix[0].length && matrix[newRow][newColumn] > matrix[row][column])
dp[row][column] = Math.max(dp[row][column],dfs(matrix,newRow,newColumn,dp)+1);
}
return dp[row][column];
}
给定一个已排序的正整数数组 nums,和一个正整数 n 。从 [1, n] 区间内选取任意个数字补充到 nums 中,使得 [1, n] 区间内的任何数字都可以用 nums 中某几个数字的和来表示。请输出满足上述要求的最少需要补充的数字个数。
解答
public int minPatches(int[] nums, int n) {
int res = 0, i = 0;
long miss = 1;
while (miss <= n) {
if (i < nums.length && nums[i] <= miss)
miss += nums[i++];
else {
miss += miss;
res++;
}
}
return res;
}
分析
序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。
例如,上面的二叉树可以被序列化为字符串 “9,3,4,#,#,1,#,#,2,#,6,#,#”,其中 # 代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 ‘#’ 。
你可以认为输入格式总是有效的,例如它永远不会包含两个连续的逗号,比如 “1,3” 。
解答
public boolean isValidSerialization(String preorder) {
int slotNumber = 1;
String[] points = preorder.split(",");
for(String point: points){
slotNumber--;
if(slotNumber < 0)return false;
if(!"#".equals(point))
slotNumber+=2;
}
return slotNumber == 0;
}
分析
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
提示:
public List<String> findItinerary(List<List<String>> tickets) {
List<String> ans = new LinkedList<>();
if (tickets == null || tickets.size() == 0)
return ans;
Map<String, PriorityQueue<String>> graph = new HashMap<>();
for (List<String> pair : tickets) {
PriorityQueue<String> nbr = graph.computeIfAbsent(pair.get(0), k -> new PriorityQueue<>());
nbr.add(pair.get(1));
}
visit(graph, "JFK", ans);
return ans;
}
// DFS方式遍历图,当走到不能走为止,再将节点加入到答案
private void visit(Map<String, PriorityQueue<String>> graph, String src, List<String> ans) {
PriorityQueue<String> nbr = graph.get(src);
while (nbr != null && nbr.size() > 0) {
// 有邻居 递归
String dest = nbr.poll();//出队 因为是优先队列 每次取出的都是字典序最小的值
visit(graph, dest, ans);
}
ans.add(0, src); // 没有邻居了 逆序插入
}
分析
给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。
数学表达式如下:
如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,
使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。
解答
// 方法一
public boolean increasingTriplet(int[] nums) {
if(nums.length < 3)return false;
List<Integer> list = new ArrayList<>();
list.add(nums[0]);
for (int i = 1; i < nums.length; i++) {
if (nums[i] > list.get(list.size() - 1)) {
list.add(nums[i]);
if (list.size() == 3) return true;
} else {
for (int j = 0; j < list.size(); j++) {
if (list.get(j) == nums[i])
break;
if (list.get(j) > nums[i]) {
list.set(j, nums[i]);
break;
}
}
}
}
return false;
}
// 方法二
public boolean increasingTriplet(int[] nums) {
int min = Integer.MAX_VALUE;
int second = Integer.MAX_VALUE;
for(int i = 0;i<nums.length;i++){
if(nums[i] == min || nums[i] == second){
continue;
}
else if(nums[i] < min){
min = nums[i];
}else if(nums[i] < second){
second = nums[i];
}else if(nums[i] > second)return true;
}
return false;
}
分析
方法一
方法二
提交结果
方法一
方法二
给定一个含有 n 个正数的数组 x。从点 (0,0) 开始,先向北移动 x[0] 米,然后向西移动 x[1] 米,向南移动 x[2] 米,向东移动 x[3] 米,持续移动。也就是说,每次移动后你的方位会发生逆时针变化。
编写一个 O(1) 空间复杂度的一趟扫描算法,判断你所经过的路径是否相交。
解答
public boolean isSelfCrossing(int[] x) {
if(x.length < 4)return false;
if(x[2] <= x[0] && x[3] >= x[1])return true;
if(x.length > 4 && x[3] <= x[1] && x[4] >= x[2])return true;
if(x.length > 4 && x[3] == x[1] && x[4] + x[0] >= x[2])return true;
for(int i = 5;i < x.length;i++){
if(x[i-1] <= x[i-3] && x[i] >= x[i-2])return true;
if(x[i-1] <= x[i-3] && x[i-4] <= x[i-2] && x[i] + x[i-4] >= x[i-2] && x[i-5] + x[i-1] >= x[i-3])return true;
}
return false;
}
分析
给定一组 互不相同 的单词, 找出所有不同 的索引对(i, j),使得列表中的两个单词, words[i] + words[j] ,可拼接成回文串。
解答
class Node{
int[] ch = new int[26];
int flag;
public Node(){
flag = -1;
}
}
List<Node> tree = new ArrayList<>();
public List<List<Integer>> palindromePairs(String[] words) {
tree.add(new Node());
for(int i = 0;i<words.length;i++){
//将单词插入前缀树中,并在最后一个字母处标记好属于第几个单词。
insert(words[i],i);
}
List<List<Integer>> res = new ArrayList<>();
for(int i = 0;i<words.length;i++){
//遍历words数组
int len = words[i].length();
for(int j = 0;j<=len;j++){
//遍历分割点
if(j != 0 && isPalindrome(words[i],0,j-1)){
//若 0 ~ j-1是回文字符串,那么就去寻找有没有 j~len-1字符串的逆序字符串,可拼接成回文字符串。
int leftIndex = findWord(words[i],j,len-1);
if(leftIndex != - 1 && leftIndex != i){
res.add(Arrays.asList(leftIndex,i));
}
}
if(isPalindrome(words[i],j,len-1)){
//若 j~len-1 是回文字符串,那么就寻找有没有0~j-1字符串的逆序字符串,可拼接成回文字符串。
int rightIndex = findWord(words[i],0,j-1);
if(rightIndex != -1 && rightIndex != i){
res.add(Arrays.asList(i,rightIndex));
}
}
}
}
return res;
}
// 插入前缀树
public void insert(String str,int index){
int add = 0;
for(int i = 0;i<str.length();i++){
int x = str.charAt(i) - 'a';
if(tree.get(add).ch[x] == 0){
tree.add(new Node());
tree.get(add).ch[x] = tree.size()-1;
}
add = tree.get(add).ch[x];
}
tree.get(add).flag = index;
}
// 判断字符串是否回文
public boolean isPalindrome(String str,int start,int end){
int len = end - start + 1;
for(int i = 0;i < len/2;i++){
if(str.charAt(start + i) != str.charAt(end-i))
return false;
}
return true;
}
//寻找前缀树中匹配的字符串。 返回下标
public int findWord(String str,int left,int right){
int add = 0;
for(int i = right;i>= left;i--){
int index = str.charAt(i) - 'a';
if(tree.get(add).ch[index] == 0)
return -1;
add = tree.get(add).ch[index];
}
return tree.get(add).flag;
}
分析
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
解答
// 方法一
Map<TreeNode,Integer> rob = new HashMap<>();
Map<TreeNode,Integer> notRob = new HashMap<>();
public int rob(TreeNode root) {
if(root == null) return 0;
dfs(root);
return Math.max(rob.get(root),notRob.get(root));
}
public void dfs(TreeNode root){
if(root == null)return;
dfs(root.left);
dfs(root.right);
rob.put(root,root.val + notRob.getOrDefault(root.left,0) + notRob.getOrDefault(root.right,0));
notRob.put(root,Math.max(notRob.getOrDefault(root.left,0),rob.getOrDefault(root.left,0))+ Math.max(notRob.getOrDefault(root.right,0),rob.getOrDefault(root.right,0)));
}
// 方法二
public int rob(TreeNode root) {
if(root == null) return 0;
int[] rootStatus = dfs(root);
return Math.max(rootStatus[0],rootStatus[1]);
}
public int[] dfs(TreeNode root){
if(root == null)return new int[]{
0,0};
int[] l = dfs(root.left);
int[] r = dfs(root.right);
int rob = root.val + l[1] + r[1];
int notRob = Math.max(l[0],l[1]) + Math.max(r[0],r[1]);
return new int[]{
rob,notRob};
}
分析
提交结果
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
进阶:
给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
要求算法的空间复杂度为O(n)。
你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
解答
//方法一
public int[] countBits(int num) {
int[] res = new int[num+1];
for(int i = 0;i<res.length;i++){
res[i] = count(i);
}
return res;
}
public int count(int x){
int count = 0;
while(x > 0){
count++;
x &= x-1;
}
return count;
}
//方法二
public int[] countBits(int num) {
int[] res = new int[num+1];
res[0] = 0;
for(int i = 1;i<res.length;i++){
if(i % 2 == 0)
res[i] = res[i/2];
else
res[i] = res[i-1] + 1;
}
return res;
}
分析
提交结果