关于数据库的题目和写脚本的题目略过
所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来查找 DNA 分子中所有出现超过一次的 10 个字母长的序列(子串)。
public List<String> findRepeatedDnaSequences(String s) {
Set<String> set = new HashSet<>();
List<String> list = new ArrayList<>();
for (int i = 0; i < s.length() - 9; i++) {
String s1 = s.substring(i,i+10);
if(set.contains(s1) && !list.contains(s1))
list.add(s1);
else set.add(s1);
}
return list;
}
分析
1.窗口大小为10,每次截取字符串s中窗口大小为10的子串,若set集合中存在 ,则表示这个子串重复,list不包含该子串是为了避免添加进去重复的子串。
2.若set中不存在,则将该子串添加到set集合,表示第一次出现。
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
public static int maxProfit(int k, int[] prices) {
if (k == 0 || prices.length == 0) return 0;
int n = prices.length;
if (k / 2 > n) return minProfit(prices);//若k/2大于n表示,和k为无穷大一个意思。
int[][][] dp = new int[n][k + 1][2];
for (int i = 0; i < n; i++) {
for (int j = k; j >= 1; j--) {
if (i == 0) {//第一天初始化
dp[i][j][0] = 0;
dp[i][j][1] = -prices[i];
continue;
}
//动态转移方程
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
}
}
return dp[n - 1][k][0];
}
public static int minProfit(int[] prices) {
int n = prices.length;
int dp_0 = 0;
int dp_1 = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
int temp = dp_0;
dp_0 = Math.max(dp_0, dp_1 + prices[i]);
dp_1 = Math.max(dp_1, temp - prices[i]);
}
return dp_0;
}
分析
1.动态规划实现,dp[i][k][j]
i表示天数 k表示最大交易次数 j表示状态 0为不持有股票 1为持有股票
2.
dp[0][k][1] 第一天持有股票 所以初始化为-prices[i]
dp[0][k][0] 第一天不持有股票初始化为0
dp[i][k][1] 第i的持有股票的状态是第i-1天的状态转换过来的
分为两种
第一种第i-1天也持用股票
dp[i][k][1] = dp[i-1][k][1]
第二种第i-1天没有持有股票 说明第i天买入了股票
dp[i][k][1] = dp[i-1][k-1][0] - prices[i]
选择大的一个作为dp[i][k][1]
dp[i][k][0] 同样分为两种
第一种第i-1天没有股票
dp[i][k][0] = dp[i-1][k][0]
第二种第i-1天持有股票,说明第i天卖出了股票
dp[i][k][0] = dp[i-1][k][1] + prices[i]
3.若k / 2 > n 题目就和k没关系了。转换成之前做过的交易任意次可获得最大利润
4.可以将三维数组转换成2维,因为第一维天数,之和前一天有关。
所以动态转移方程可以改为
dp[j][0] = Math.max(dp[j][0], dp[j][1] + prices[i]);
dp[j][1] = Math.max(dp[j][1], dp[j-1][0] - prices[i]);
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
public static void rotate(int[] nums, int k) {
k = k % nums.length;
if (nums.length < 2 || k == 0) return;
reverse(nums,0,nums.length-1);
reverse(nums, 0, k-1);
reverse(nums, k, nums.length - 1);
}
//反转数组
public static void reverse(int[] nums, int start, int end) {
while (start < end) {
int tmp = nums[start];
nums[start] = nums[end];
nums[end] = tmp;
start++;
end--;
}
}
分析
1.先对数组全体反转
2.对前k个反转
3.对剩余的反转
例如 1,2,3,4,5,6,7 k=2
先全体反转7,6,5,4,3,2,1
前k个反转5,6,7,4,3,2,1
剩余反转5,6,7,1,2,3,4 即为答案
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。
public int reverseBits(int n) {
int power = 31;
int res = 0;
for (int i = 0; i <= power; i++) {
res += (1 & (n >> i))<<(power-i);
}
return res;
}
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
public int hammingWeight(int n) {
int sum = 0;
for (int i = 0; i<=31 ; i++) {
sum += (1&(n>>i)) == 1 ? 1:0;
}
return sum;
}
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
public int rob(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
int[][] dp = new int[n + 1][2];
dp[0][0] = 0;
dp[0][1] = 0;
for (int i = 1; i <= n; i++) {
dp[i][0] = dp[i - 1][1] + nums[i - 1];//第i个偷
dp[i][1] = Math.max(dp[i - 1][0], dp[i - 1][1]);//第i个不偷
}
return Math.max(dp[n][0],dp[n][1]);
}
public static int rob2(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
int dp_i_0 = 0;
int dp_i_1 = 0;
for (int i = 0; i < n; i++) {
int tmp = dp_i_1;
int tmp2 = dp_i_0;
dp_i_0 = dp_i_1 + nums[i];
dp_i_1 = Math.max(tmp, tmp2);
}
return Math.max(dp_i_0, dp_i_1);
}
分析
1.动态规划实现,
dp[i][0]表示偷窃第i个小屋所能获得的最大金额
因为第i个偷 所以第i-1个不能偷
所以dp[i][0] = dp[i - 1][1] + nums[i - 1]
dp[i][1]表示不偷窃第i个小屋所能获得的最大金额
所以就返回dp[i-1][1]和dp[i-1][0]中的较大者。
dp[i][1] = Math.max(dp[i - 1][0], dp[i - 1]
2.初始化dp[0][0],dp[0][1]均为0
3.最后返回dp[n][0]和dp[n][1]中的较大者
4.因为dp[i]之和前一个dp[i-1]有关,所以可以优化一下,不需要保留所有的结果。见方法二
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
public static List<Integer> rightSideView(TreeNode root) {
List<List<Integer>> lists = new ArrayList<>();
rightsSideView(root,lists,0);
List<Integer> res = new ArrayList<>();
for(List<Integer> list : lists){
res.add(list.get(list.size()-1));
}
return res;
}
public static void rightsSideView(TreeNode root, List<List<Integer>> lists, int depth) {
if (root != null) {
if(lists.size()<=depth)
lists.add(new ArrayList<>());
lists.get(depth).add(root.val);
}else return;
rightsSideView(root.left,lists,depth+1);
rightsSideView(root.right,lists,depth+1);
}
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
public static int numIslands(char[][] grid) {
int height = grid.length;
if (height == 0)return 0;
int width = grid[0].length;
int res = 0;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (grid[i][j] == '1') {
numIslands(grid, i, j, height, width);
res++;
}
}
}
return res;
}
// 深度搜索
public static void numIslands(char[][] grid, int i, int j, int height, int width) {
if (i < 0 || i >= height || j < 0 || j >= width || grid[i][j] == '0') return;
if (grid[i][j] == '1') {
grid[i][j] = '2';
numIslands(grid, i + 1, j, height, width);
numIslands(grid, i - 1, j, height, width);
numIslands(grid, i, j + 1, height, width);
numIslands(grid, i, j - 1, height, width);
}
}