494.给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3
示例 2:输入:nums = [1], target = 1
输出:1
提示:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000
回溯和动归应该都可以
class Solution {
int count=0;
public int findTargetSumWays(int[] nums, int target) {
int length=nums.length;
dfs(nums,target,0,0,length);
return count;
}
public void dfs(int[] nums,int target,int index,int sum,int length){
if(index==length&&sum==target){
count++;
}
if(index!=length){
dfs(nums,target,index+1,sum+nums[index],length);
dfs(nums,target,index+1,sum-nums[index],length);
}
}
}
非常丑陋的结果了。。
加上记忆化搜索???
动归的传递方程都写出来了!!!有理由相信我可以写出来、、
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
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i] 仅有小写英文字母组成
wordDict 中的所有字符串 互不相同
看到字符串有限考虑动态规划,第一想法是记忆化回溯。
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
//动态规划
//dp[i]是前i个字符是否能找到匹配的字符串
//check(s[j..i-1]) 检查j以后的字符串是否能匹配
//dp[i]=dp[j]&check(s[j..i-1])
//如何实现check函数
Set<String> set=new HashSet<>(wordDict);
int length=s.length();
boolean[] dp=new boolean[length+1];
dp[0]=true;
for(int i=1;i<=length;i++){
for(int j=0;j<i;j++){
if(dp[j]&&set.contains(s.substring(j,i))){
dp[i]=true;
break;
}
}
}
return dp[length];
}
}
56.以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
class Solution {
public int[][] merge(int[][] intervals) {
//贪心算法
int length=intervals.length;
if(length==0){
return intervals;
}
int start;
int end;
List<int[]> list=new ArrayList<>();
//对intervals进行按starti从小到大排序
Arrays.sort(intervals,new Comparator<int[]>(){
public int compare(int[] a,int[] b){
return a[0]-b[0];
}
});
start=intervals[0][0];
end=intervals[0][1];
for(int i=1;i<length;i++){
if(intervals[i][0]<=end){
//更新end值
end=Math.max(end,intervals[i][1]);
}else{
//存储,更新start值为下一个,如果i已经是最后一个了就不更新了
list.add(new int[]{start,end});
start=intervals[i][0];
end=intervals[i][1];
}
}
list.add(new int[]{start,end});
return list.toArray(new int[list.size()][]);
}
}
200.给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 '0' 或 '1'
class Solution {
public int numIslands(char[][] grid) {
//深度优先遍历
int m=grid.length;
int n=grid[0].length;
int count=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]=='1'){
count++;
dfs(i,j,grid);
}
}
}
return count;
}
//包含当前目标的岛屿清零
public void dfs(int x,int y,char[][] grid){
int a=grid.length;
int b=grid[0].length;
if(x==a||y==b||x<0||y<0||grid[x][y]=='0'){
return;
}
grid[x][y]='0';
dfs(x-1,y,grid);
dfs(x+1,y,grid);
dfs(x,y+1,grid);
dfs(x,y-1,grid);
}
}
bfs搞一哈
class Solution {
public int numIslands(char[][] grid) {
//深度优先遍历
int m=grid.length;
int n=grid[0].length;
int count=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]=='1'){
count++;
bfs(i,j,grid);
}
}
}
return count;
}
//包含当前目标的岛屿清零
public void bfs(int x,int y,char[][] grid){
Queue<int[]> q=new LinkedList<>();
q.add(new int[]{x,y});
while(!q.isEmpty()){
int[] temp=q.poll();
int a=temp[0];
int b=temp[1];
if(a<grid.length&&a>=0&&b<grid[0].length&&b>=0&&grid[a][b]=='1'){
grid[a][b]='0';
bfs(a-1,b,grid);
bfs(a+1,b,grid);
bfs(a,b+1,grid);
bfs(a,b-1,grid);
}
}
}
}
学习一下并查集--以防不能修改原数组的情况
public class Solution {
private int rows;
private int cols;
public int numIslands(char[][] grid) {
rows = grid.length;
if (rows == 0) {
return 0;
}
cols = grid[0].length;
// 空地的数量
int spaces = 0;
UnionFind unionFind = new UnionFind(rows * cols);
int[][] directions = {{1, 0}, {0, 1}};
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '0') {
spaces++;
} else {
// 此时 grid[i][j] == '1'
for (int[] direction : directions) {
int newX = i + direction[0];
int newY = j + direction[1];
// 先判断坐标合法,再检查右边一格和下边一格是否是陆地
if (newX < rows && newY < cols && grid[newX][newY] == '1') {
unionFind.union(getIndex(i, j), getIndex(newX, newY));
}
}
}
}
}
return unionFind.getCount() - spaces;
}
private int getIndex(int i, int j) {
return i * cols + j;
}
private class UnionFind {
/**
* 连通分量的个数
*/
private int count;
private int[] parent;
public int getCount() {
return count;
}
public UnionFind(int n) {
this.count = n;
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
private int find(int x) {
while (x != parent[x]) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
public void union(int x, int y) {
int xRoot = find(x);
int yRoot = find(y);
if (xRoot == yRoot) {
return;
}
parent[xRoot] = yRoot;
count--;
}
}
}
406.假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
示例 2:输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
提示:
1 <= people.length <= 2000
0 <= hi <= 106
0 <= ki < people.length
题目数据确保队列可以被重建
class Solution {
public int[][] reconstructQueue(int[][] people) {
//身高从高到底,人数从低到高。
Arrays.sort(people,new Comparator<int[]>(){
public int compare(int[] a,int[] b){
if(a[0]==b[0]){
return a[1]-b[1];
}else{
return b[0]-a[0];
}
}
});
List<int[]> list=new LinkedList<>();
//遍历二维数组,进行插入。这一步按照k进入插入也太妙了吧
for(int[] i:people){
list.add(i[1],i);
}
return list.toArray(new int[list.size()][]);
}
}
146.请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。示例:
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 105
最多调用 2 * 105 次 get 和 put
面试官希望可以独立实现一个哈希表和双向链表的类,直接调用不行。
1.哈希表+双向链表
LRU算法需要在缓存满的时候清理最久没用的,那么一定需要时序;而且要通过key快速找到value,每次访问一个key都需要把它的时序置为最新,也就是说需要能在任意位置快速删除元素,增加至尾部以标志时间顺序,通过链表实现。由key到value,用哈希表实现。
为什么是双向链表?单向为什么不可以?
因为要进行删除操作,需要前后节点才能删除。保证复杂度为常数。
//一、自定义节点类[【本题唯一需要的数据结构就是Node】]
class Node{
public int key , val;
public Node next , prev;//双向指针:next表示下一个,prev表示前一个
public Node(int k , int v){
this.key = k;
this.val = v;
}
}
//二、自定义双向链表
class DoubleList{
//并非真的需要链表List数据结构,只需要将节点Node前后的方向对应好就可以实现双向链表的效果
private Node head , tail;//head是头节点,tail是尾节点
private int size;
//在链表头部添加节点,及时更新头节点head和size
public void addFirst(Node node){
if(head == null){
head = tail = node;
}else{
//将head与node变换位置,使得node在前,head在后,即node→head
Node n = head;
n.prev = node;//在头结点head之前
node.next = n;
//交换之后要及时更新头节点head
head = node;
}
size++;
}
//删除链表中的x节点(x一定存在),及时更新尾节点tail、头节点head和size
public void remove(Node node){
//需要讨论node在链表中的位置
if(head == node && tail == node){
head = null;
tail = null;
}else if(tail == node){
node.prev.next = null;//删除node节点
tail = node.prev;//更新尾部节点
}else if(head == node){
node.next.prev = null;//删除
head = node.next;//更新
}else{
node.prev.next = node.next;
node.next.prev = node.prev;
}
size--;
}
//删除链表中最后一个节点,并返回该节点
public Node removeLast(){
Node node = tail;
remove(tail);
return node;
}
//返回链表长度
public int size(){
return size;
}
}
//三、实现满足LRU缓存的数据结构
class LRUCache {
//使用哈希表存放key,Node(key,val),实现快速查找、插入、删除
private HashMap<Integer , Node> map;
//使用自定义双向链表实现最久未使用(垃圾)的关键字的删除,即垃圾回收,将垃圾(最久未使用的元素)放在链表尾部(删除尾部元素),最新使用的放在头部(在头部插入)
private DoubleList cache;
private int cap;//最大容量
//以 正整数 作为容量 capacity 初始化 LRU 缓存
public LRUCache(int capacity) {
this.cap = capacity;
map = new HashMap<>();//哈希表初始化
cache = new DoubleList();//双向链表初始化
}
//查找
public int get(int key) {
//如果不存在,返回-1
if(!map.containsKey(key)){
return -1;
}
int val = map.get(key).val;
//将查找的元素(key,val)提前至头部
put(key , val);
//如果存在,返回key的value值
return val;
}
//插入或更改
public void put(int key, int value) {
Node x = new Node(key , value);
//如果存在则更改
if(map.containsKey(key)){
//在双向链表中删除旧的节点
cache.remove(map.get(key));
//在双向链表头部插入新的节点
cache.addFirst(x);
//更新哈希表map中的数据
map.put(key , x);
}else{
//如果不存在则插入
//插入时先判断容量是否够用
//如果容易已满,则先删除垃圾(链表尾部元素)
if(cap == cache.size()){
Node last = cache.removeLast();//返回并删除尾部元素
map.remove(last.key);
}
//,再去插入新元素
cache.addFirst(x);
map.put(key , x);
}
}
}
要注意map和链表的同步。
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:输入:coins = [2], amount = 3
输出:-1
示例 3:输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
public class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
int[] dp = new int[amount + 1];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
198.你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
class Solution {
public int rob(int[] nums) {
//想法是动态规划 dp[i]是偷到i房屋情况下偷到的最多的钱
int length=nums.length;
int[] dp=new int[length];
if(length==1){
return nums[0];
}
if(length==2){
return Math.max(nums[0],nums[1]);
}
if(length==3){
return Math.max(nums[0]+nums[2],nums[1]);
}
dp[0]=nums[0];
dp[1]=nums[1];
dp[2]=nums[0]+nums[2];
int max=Math.max(dp[0],dp[1]);
max=Math.max(max,dp[2]);
for(int i=3;i<length;i++){
dp[i]=nums[i]+Math.max(dp[i-2],dp[i-3]);
max=Math.max(max,dp[i]);
}
return max;
}
}
写的有点丑陋
647.给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
1 <= s.length <= 1000
s 由小写英文字母组成
class Solution {
public int countSubstrings(String s) {
//做一个判断是否为回文串的函数,遍历所有可能的字符串组合
int length=s.length();
int count=0;
if(length==1){
return 1;
}
for(int i=0;i<length;i++){
for(int j=i+1;j<=length;j++){
String temp=s.substring(i,j);
if(isSubString(temp)){
count++;
}
}
}
return count;
}
public boolean isSubString(String s){
int length=s.length();
char[] c=s.toCharArray();
if(length==1){
return true;
}
int left=length/2-1;
int right;
if(length%2==0){
right=length/2;
}else{
right=length/2+1;
}
while(left>=0){
if(c[left]!=c[right]){
return false;
}
left--;
right++;
}
return true;
}
}
时间复杂度很高,有专门的算法可以优化。
Manacher 算法是在线性时间内求解最长回文子串的算法。
public int countSubstrings(String s) {
int n = s.length();
StringBuffer t = new StringBuffer("$#");
// 解决回文串奇数长度和偶数长度的问题,处理方式是在所有的相邻字符中间插入 # ,这样可以保证所有找到的回文串都是奇数长度的
for (int i = 0; i < n; ++i)
t.append(s.charAt(i)).append('#');
t.append('!');// 字符串边界外,再随便放两个不一样的字符,防越界。e.g. aba → $#a#b#a#!
n = t.length();
int[] f = new int[n];// 用 f(i) 来表示以 t 的第 i 位为回文中心,可以拓展出的最大回文半径(臂长)
int j = 0, right = 0;// 维护「当前最大的回文的右端点 right」以及这个回文右端点对应的回文中心 j
int ans = 0;
for (int i = 2; i < n - 2; ++i) {
// i 被包含在当前最大回文子串内(right与当前点的距离, i关于j对称的点的f值),不被包含(0)
// 这里将 right−i 和 f[对称点] 取小,是先要保证这个回文串在当前最大回文串内。
f[i] = i <= right ? Math.min(right - i, f[2 * j - i]) : 0; // 初始化 f[i]
while (t.charAt(i + f[i] + 1) == t.charAt(i - f[i] - 1))// 中心拓展
++f[i];
if (i + f[i] > right) {// 动态维护 iMax 和 rMax
j = i;
right = i + f[i];
}
ans += (f[i] + 1) / 2;// 统计答案, 臂长为1,贡献一个答案(字符自己),臂长为3,贡献2个...
}
return ans;
}
253.给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。
示例 1:
输入:intervals = [[0,30],[5,10],[15,20]]
输出:2
示例 2:输入:intervals = [[7,10],[2,4]]
输出:1
提示:
1 <= intervals.length <= 104
0 <= starti < endi <= 106
广联达面试出了一个变体。
想法是类似一个坐标轴的,统计最大纵坐标。转化成上下车问题。
class Solution {
public int minMeetingRooms(int[][] intervals) {
//上下车
int length=intervals.length;
int[][] temp=new int[length*2][2];
int i=0;
for(int[] arr:intervals){
temp[i++]=new int[]{arr[0],1};
temp[i++]=new int[]{arr[1],-1};
}
Arrays.sort(temp,new Comparator<int[]>(){
public int compare(int[] a,int[] b){
return a[0]==b[0]?a[1]-b[1]:a[0]-b[0];
}
});
int curr=0;
int max=0;
for(int[] arr:temp){
curr=curr+arr[1];
max=Math.max(max,curr);
}
return max;
}
}