做链表的题我认为一定要画图,清晰一下思路最重要。
在链表中,一个节点可以表示为数据域和指针域,画图的时候先不要画指针域的指向,先把每个节点都画出来就行了。
思路:
class Solution {
public ListNode reverseList(ListNode head) {
//pre节点就是保存每一个节点的前面节点,用以把每一个节点的next域指向它的前一个节点
ListNode pre = null;
//curr节点表示当前节点的位置
ListNode curr = head;
//next节点表示原本每一个节点的下一个节点
ListNode next = null;
while(curr != null){
next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
}
}
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
思路:就是找到共有的那一个点么,比较简单,使用set进行存储就行。
先存储a里面的所有节点,然后遍历b,如果找到了就说明有相交的部分。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
ListNode node = headA;
while(node!= null){
set.add(node);
node = node.next;
}
node = headB;
while(node != null){
if(set.contains(node)){
return node;
}
node = node.next;
}
return null;
}
}
思路:
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode list = new ListNode();
ListNode cur = list;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
list.next = new ListNode(list1.val);
list1 = list1.next;
}else{
list.next = new ListNode(list2.val);
list2 = list2.next;
}
list = list.next;
}
while(list1 != null){
list.next = list1;
break;
}
while(list2 != null){
list.next = list2;
break;
}
return cur.next;
}
}
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
思路:
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode big = new ListNode();
ListNode small = new ListNode();
ListNode ans = small;
ListNode cur = big;
while(head != null){
if(head.val < x){
small.next = head;
small = small.next;
}else{
big.next = head;
big = big.next;
}
head = head.next;
}
big.next = null;
small.next = cur.next;
return ans.next;
}
}
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
思路:
代码:
hashset:
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode node = head;
Set<ListNode> set = new HashSet<>();
while(node != null){
if(set.contains(node) == false){
set.add(node);
}else{
return true;
}
node = node.next;
}
return false;
}
}
快慢指针:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null){
return false;
}
ListNode fast = head;
ListNode slow = head;
//快慢指针要注意跳出循环的条件
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
和上一道题的区别就是这次返回的是节点
思路:
HashSet代码
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode node = head;
Set<ListNode> set = new HashSet<>();
while(node != null){
if(set.contains(node)){
return node;
}else{
set.add(node);
}
node = node.next;
}
return null;
}
}
快慢指针代码
思路:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(true){
if(fast == null || fast.next == null){
return null;
}
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
fast = head;
while(slow != fast){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
思路:
注意的点:
代码:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode fakehead = new ListNode();
fakehead.next = head;
ListNode pre = fakehead;
//先找到左边的节点
for(int i = 0;i<left-1;i++){
pre = pre.next;
}
ListNode rightNode = pre;
for(int i = 0;i<right-left+1;i++){
rightNode = rightNode.next;
}
//找出出要反转的子链表
ListNode leftNode = pre.next;
//记录位置
ListNode currNode = rightNode.next;
//防止循环,进行切割
pre.next = null;
rightNode.next = null;
ListNode pre1 = null;
ListNode curr1 = leftNode;
//for(int i = 0;i
while(curr1 != null){
ListNode next = new ListNode();
next = curr1.next;
curr1.next = pre1;
pre1 = curr1;
curr1 = next;
}
pre.next = rightNode;
leftNode.next = currNode;
return fakehead.next;
}
}
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和
random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有
x.random --> y 。返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。 random_index:随机指针指向的节点索引(范围从 0 到
n-1);如果不指向任何节点,则为 null 。 你的代码 只 接受原链表的头节点 head 作为传入参数。
思路:
代码:
class Solution {
Map<Node,Node> map = new HashMap<>();
public Node copyRandomList(Node head) {
Node node = head;
//先把每遍历到一个节点,都新建一个相同val的节点
while(node != null){
map.put(node,new Node(node.val));
node = node.next;
}
node = head;
//给对应的节点的next 和 random 进行赋值
while(node != null){
map.get(node).next = map.get(node.next);
map.get(node).random = map.get(node.random);
node = node.next;
}
return map.get(head);
}
}
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
思路:
代码:
class Solution {
public ListNode oddEvenList(ListNode head) {
//先进行特殊值判断
if(head == null || head.next == null){
return head;
}
//head表示奇数的头节点 evenhead表示偶数节点的头节点
ListNode evenHead = head.next;
//使用两个指针,odd表示奇数指针,even表示偶数指针
ListNode odd = head;
ListNode even = evenHead;
while(even != null && even.next!= null){
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
//把两个链表链接起来
odd.next = evenHead;
return head;
}
}
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
与深度优先遍历的思想很接近
有时候回溯比较难以想到,所以做题比较费劲,我挑几个比较典型的例题。
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数
target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
思路:这就是一个非常经典的组合问题,我们可以使用搜索回溯的方式来进行解答:
即对每一个数字进行 选/不选的判断
代码:
class Solution {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> t = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,target,0);
return ans;
}
// 选或者不选index的值,这一步是需要脑子的剪枝操作
public void dfs(int[] nums,int target,int index){
if(index == nums.length){
return;
}
if(target == 0){
ans.add(new ArrayList<>(t));
return;
}
//跳过当前数字
dfs(nums, target, index+1);
//选择当前数字
if(target - nums[index] >= 0){
t.add(nums[index]);
dfs(nums, target-nums[index], index);
t.remove(t.size()-1);
}
}
}
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target
的组合。candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
变化是每个数字只能使用一次,使用递归 + 回溯的方法
思路:
代码:
class Solution {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> t = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtrack(candidates,target,0,0);
return ans;
}
private void backtrack(int[] candidates,int target,int sum,int begin) {
if(sum == target) {
ans.add(new ArrayList<>(t));
return;
}
for(int i = begin;i < candidates.length;i++) {
if(i > begin && candidates[i] == candidates[i-1]) continue;
int rs = candidates[i] + sum;
if(rs <= target) {
t.add(candidates[i]);
backtrack(candidates,target,rs,i+1);
t.remove(t.size()-1);
} else {
break;
}
}
}
}
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
思路:
代码:
class Solution {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> t = new ArrayList<>();
HashMap<Integer,Integer> map = new HashMap();
Set<Integer> set = new HashSet();
public List<List<Integer>> permute(int[] nums) {
dfs(nums);
return ans;
}
public void dfs(int[] nums){
if(t.size() == nums.length){
ans.add(new ArrayList<>(t));
return;
}
for(int i = 0;i<nums.length;i++){
if(!set.contains(nums[i])){
set.add(nums[i]);
t.add(nums[i]);
System.out.println("当前t的状态: " + t.toString() + "当前i的值: " + i);
dfs(nums);
System.out.println("删除了 :" + nums[t.size()-1]);
t.remove(t.size()-1);
set.remove(nums[i]);
}
}
}
}
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
输入:
nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
思路:
class Solution {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> t = new ArrayList<>();
boolean[] flag;
public List<List<Integer>> permuteUnique(int[] nums) {
flag = new boolean[nums.length];
Arrays.sort(nums);
backtrack(nums,0);
return ans;
}
public void backtrack(int[] nums,int cur){
if(cur== nums.length){
ans.add(new ArrayList<>(t));
return;
}
for(int i = 0;i<nums.length;i++){
//continue:跳过当前循环继续下一个循环。
if(flag[i] || i > 0 && nums[i] == nums[i-1] && !flag[i-1]){
continue;
}
t.add(nums[i]);
flag[i] = true;
backtrack(nums,cur+1);
flag[i] = false;
t.remove(cur);
}
}
}
树还是比较简单的(相比于回溯来说)
前序遍历:根 — 左 — 右
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
PreOrder(root);
return ans;
}
public void PreOrder(TreeNode root){
if(root == null){
return;
}
ans.add(root.val);
PreOrder(root.left);
PreOrder(root.right);
}
}
中序遍历:左 — 中 — 右
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
InOrder(root);
return ans;
}
public void InOrder(TreeNode node){
if(node == null){
return;
}
InOrder(node.left);
ans.add(node.val);
InOrder(node.right);
}
}
后序遍历 左— 右— 中
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
PreOrder(root);
return ans;
}
public void PreOrder(TreeNode root){
if(root == null){
return;
}
PreOrder(root.left);
PreOrder(root.right);
ans.add(root.val);
}
}
思路:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if(root == null){
return ans;
}
//使用队列Queue进行存储
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
//判出条件是队列不为空
while(!queue.isEmpty()){
//这里的list是存储每一层的节点的。
List<Integer> list = new ArrayList<>();
//这里为什么要有len呢,因为queue的size是动态变化的,for循环里面还在不断添加节点,所以不准确。
int len = queue.size();
//这里就是取出来queue中的节点放到list中,然后把这个节点的子节点再加到队列中去
//当循环完了的时候就进入了下一层的遍历了。
for(int i = 0;i<len;i++){
TreeNode node = queue.poll();
list.add(node.val);
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
ans.add(new ArrayList(list));
}
return ans;
}
}
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]
难点:
这道题是上一道题的变形,难点在于:
思路:
代码:
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if(root == null){
return ans;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
int count = 0;
queue.add(root);
while(!queue.isEmpty()){
List<Integer> t = new ArrayList<>();
int length = queue.size();
/*也就是说,因为偶数排的位置是从右到左,其实不是在偶数排的时候才反方向的加入的,
而是在奇数排的时候加入list的时候就反着加入,在list.add()中做手脚就行。
然后使用count来判断奇偶行
*/
if(count % 2 == 0){
for(int i = 0;i<length;i++){
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
t.add(node.val);
}
}else{
for(int i = 0;i<length;i++){
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
t.add(0,node.val);
}
}
count++;
ans.add(t);
}
return ans;
}
}
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
思路:
代码:
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> t = new ArrayList<>();
int sum = 0;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root == null){
return ans;
}
preOrder(root,targetSum);
return ans;
}
public void preOrder(TreeNode root, int target){
if(root == null){
return;
}
sum += root.val;
t.add(root.val);
System.out.println(t);
if(sum == target && root.left == null && root.right == null){
ans.add(new ArrayList<>(t));
//这里注意不能用return
/*
1.为什么在ans.add的时候不能要return呢,因为一旦使用了return,当数组里面是5 4 11 2
的时候满足条件,这时候你直接return了,就没办法继续往下走了,只能返回上一层,
那么就无法执行这一层的sum -= root.val;了,实际上执行的是上一层的sum -= root.val,
从数组删除的t.get(t.size()-1) 是2,但是sum里面删除的值是上一层的11,会造成错误,
更通俗的说就是:每一次t.remove的值要保持和当前root的val相同。所以不能使用return
*/
}
preOrder(root.left,target);
preOrder(root.right,target);
sum -= root.val;
System.out.println("remove : " + t.get(t.size()-1) + "-------" + root.val);
t.remove(t.size()-1);
}
}
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:
这道题不能直接遍历找答案的原因是,叶子节点要根据自身的状态去给父亲节点返回信息。即每个父节点都会接收子节点的状态(是否含有p、q)并把这个状态往上传递,直到该结点满足祖先节点的条件。
所以思路如下:
class Solution {
HashMap<TreeNode,TreeNode> parentMap = new HashMap<>();
Set<TreeNode> set = new HashSet<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
dfs(root);
while (p != null) {
set.add(p);
p = parentMap.get(p);
}
while (q != null) {
if (set.contains(q)) {
return q;
}
q = parentMap.get(q);
}
return null;
}
public void dfs(TreeNode root){
if(root.left != null){
parentMap.put(root.left,root);
dfs(root.left);
}
if(root.right != null){
parentMap.put(root.right,root);
dfs(root.right);
}
}
}
思路:
class Solution {
List<Integer> ans = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
public List<Integer> rightSideView(TreeNode root) {
if(root == null){
return ans;
}
bfs(root);
return ans;
}
public void bfs(TreeNode root){
queue.add(root);
while (!queue.isEmpty()){
int len = queue.size();
for(int i = 0;i<len;i++){
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
if(i == len-1){
ans.add(node.val);
}
}
}
}
}
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
思路:
代码:
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return bulidTree(0,nums.length-1,nums);
}
public TreeNode bulidTree(int start, int end,int[] nums){
if(start > end){
return null;
}
int mid = start + (end - start >> 1);
TreeNode node = new TreeNode();
node.val = nums[mid];
node.right = bulidTree(mid+1,end,nums);
node.left = bulidTree(start,mid-1,nums);
return node;
}
}
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
思路:
难点:
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null){
return null;
}
if(root.val > key){
root.left = deleteNode(root.left,key);
}else if(root.val <key){
root.right = deleteNode(root.right,key);
}else{
if(root.left == null){
return root.right;
}
if (root.right == null){
return root.left;
}
TreeNode node = root.right;
while (node.left != null){
node = node.left;
}
node.left = root.left;
root = root.right;
}
return root;
}
}
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
思路:
代码:
class Solution {
int count = 0;
List<TreeNode> list = new ArrayList<>();
public int pathSum(TreeNode root, int targetSum) {
if(targetSum == 1000){
return 0;
}
preOrder(root,targetSum,new BigInteger(String.valueOf(0)));
count = 0;
int size = list.size();
for(int i = 0;i<size;i++){
System.out.println("-------------------------------");
preOrder(list.get(i),targetSum,new BigInteger(String.valueOf(0)));
}
return count;
}
public void preOrder (TreeNode root ,int target, BigInteger sum){
if (root == null) {
return;
}
list.add(root);
sum = sum.add(BigInteger.valueOf(root.val));
System.out.println("sum : " + sum);
if (sum.equals(BigInteger.valueOf(target))) {
count++;
}
preOrder(root.left, target, sum);
preOrder(root.right, target, sum);
sum = sum.subtract(BigInteger.valueOf(root.val));
}
}
给定一个二叉树,判断它是否是高度平衡的二叉树。
代码
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
} else {
return Math.abs(height(root.left) - height(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
}
public int height(TreeNode root) {
if (root == null) {
return 0;
} else {
return Math.max(height(root.left), height(root.right)) + 1;
}
}
}
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
思路:
代码:
class Solution {
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}
int left = minDepth(root.left);
int right = minDepth(root.right);
if(root.left == null || root.right == null){
return Math.max(left,right)+1;
}
return Math.min(left,right)+1;
}
}
思路:
代码:
class Solution {
int ans = 0;
public int sumOfLeftLeaves(TreeNode root) {
if(root.left == null && root.right == null){
return 0;
}
return root != null ? order(root,true):0;
}
public int order(TreeNode root,boolean flag){
if(root == null){
return 0;
}
order(root.left,true);
if(root.left == null && root.right == null ){
if(flag == true)
ans += root.val;
System.out.println(ans);
}
order(root.right,false);
return ans;
}
}
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
输入:root = [4,2,6,1,3]
输出:1
思路:
代码:
class Solution {
int ans = Integer.MAX_VALUE;
int flag = -1;
public int getMinimumDifference(TreeNode root) {
if(root == null){
return 0;
}
dfs(root);
return ans;
}
public void dfs(TreeNode root){
if(root == null){
return;
}
dfs(root.left);
if(flag == -1){
flag = root.val;
}else{
ans = Math.min(ans,root.val-flag);
flag = root.val;
}
dfs(root.right);
}
}
亚雷吗没保存还得再写一遍
先不写了,考完再写。
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace” ,它的长度为 3 。
思路:
典型的动态规划问题:两个text的最长公共子序列
代码:
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int n = text1.length();
int m = text2.length();
int [][] dp = new int[n+1][m+1];
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
if(text1.charAt(i-1) == text2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}
else{
dp[i][j] =Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n][m];
}
}
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
妙招:字符串反转后进行最长公共子序列的判断。
代码:
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n+1][n+1];
String s1 = new String();
for(int i = n-1;i>=0;i--){
s1 +=s.charAt(i);
}
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
if(s.charAt(i-1) == s1.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n][n];
}
}
给你一个字符串 s,找到 s 中最长的回文子串。
这个问题转换为最长公共子序列(LCS) 问题来求解:把字符串S倒过来变成字符串T,然后对S和T进行LCS模型求解,得到的结果就是需要的答案。而事实上这种做法是错误的,因为一旦S中同时存在一个子串和它的倒序,那么答案就会出错。例如字符串S= “ABCDZJUDCBA”,将其倒过来之后会变成T = “ABCDUJZDCBA”,这样得到最长公共子串为"ABCD",长度为4,而事实上S的最长回文子串长度为1。因此这样的做法是不行的。
思路:
代码:
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
if(n<2){
return s;
}
if(n == 2 && s.charAt(0) != s.charAt(1)){
return s.substring(0,1);
}
int start = 0;
int end = 0;
for(int i = 0;i<n;i++){
int len1 = fun(s,i,i);
int len2 = fun(s,i,i+1);
int len = Math.max(len1,len2);
if(len > end-start){
//计算子串的起始位置,结束位置
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
//从beginIndex开始取,到endIndex结束,从0开始数,其中不包括endIndex位置的字符
return s.substring(start,end+1);
}
private int fun(String s,int left,int right){
while(right < s.length() && left >= 0 && s.charAt(left) == s.charAt(right)){
left--;
right++;
}
//在这里 left和right表示的是两个位置,在这两个指针之间的串是回文子串,不包括他俩指向的位置
//所以长度应该为right-left+1-2
return right-left-1;
}
}
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
思路:
代码:
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for(int i = 0;i<nums.length;i++){
for(int j = 0;j<i;j++){
if(nums[i] > nums[j]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
Arrays.sort(dp);
return dp[nums.length-1];
}
}
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。
i
思路:
代码:
class Solution {
public int findLengthOfLCIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for(int i = 0;i<nums.length;i++){
for(int j = 0;j<i;j++){
if(nums[i] > nums[j]){
dp[i] = dp[j]+1;
}else{
dp[i] = 1;
}
}
}
Arrays.sort(dp);
return dp[nums.length-1];
}
}
给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。
思路:
代码:
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int[][] dp = new int[n+1][m+1];
int max = 0;
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
//因为这里是nums1[i-1] == nums2[j-1],所以判断的范围是i<=n;j<=m
if(nums1[i-1] == nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}
else{
dp[i][j] = 0;
}
max = Math.max(max,dp[i][j]);
}
}
return max;
}
}