1.数组
1.栈
1.有效的括号-20
指定一个由{}、[]、()三种组成的括号字符串,来校验是否是有效匹配的字符串。
/**
思路:
1、初始校验、判断是否为空、是否为奇数(这两种情况直接返回结束)
2、转换为字节数组、将对立的另一半压入栈中;
3、遍历压入另一半元素时,如果元素不相等、或者栈为空则认为不匹配
*/
public class Solution{
public boolean solution(String str){
// 初始校验
if(str.isEmpty()){
return false;
}
// 转换为字符数组、循环压入另一半入栈
Stack<Character> stack = new Stack<Character>();
for(char s : str.toCharArray()){
if(s == '('){
stack.push(')');
}else if(s == '['){
stack.push(']');
}else if(s == '{'){
stack.push('}');
}else if(stack.isEmpty() || s != stack.pop()){
// 为空,则表示为奇数个、不相等则表示不匹配
return false;
}
return stack.isEmpty();
}
}
}
2.链表
1.环形链表
给定一个链表、其中pos指示的是表尾部指向形成环状结点的位置,
/**
思路:
定义双指针、初始均为指向头结点
1、preNode指针每次移动两个元素
2、currNode指针每次移动一个元素
3、当两个指针相等时,则会追上,表示存在环形链表
*/
public class Solution{
public boolean solution(ListNode head){
ListNode preNode, currNode = head;
while(preNode != null && preNode.next != null){
preNode = preNode.next.next;
currNote = currNode.next;
if(currNode == preNode) return true;
}
return false;
}
}
2.反转链表
给定一个链表,返回反转后的链表
public class Solution{
public ListNode reverserNode(ListNode head){
// 定义当前指针指向头结点
ListNode curr = head;
// 定义前指针、临时指针
ListNode pre, temp = null;
while(curr != null){
// 将临时指针指向第一个结点的下一个结点
temp = curr.next;
// 将当前结点的next指向pre
curr.next = pre;
// 分别移动pre、curr
pre = curr;
curr = temp;
}
return pre;
}
}
2.Map容器
1.两数相加
给定一个不重复元素的数组、指定一个目标值、求出数组中 num1 + num2 = target元素的下标数组
/**
思路:
1、利用Map键值对的特性、存储元素值key、对应的索引下标i
2、循环遍历数组,target - nums[0]后的值加入Map中;
3、遍历第二个元素,当target -nums[1]后的值等于第一次加入Map中元素的值,则返回数组
4、如果仍然不相等,则再次加入当前值。
*/
public class Solution{
public int[] twoSum(int[] nums, int target){
// Map存储元素
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i = 0; i < nums.lenght; i++){
if(map.containsKey(target - nums[i])){
// target - 当前值后的元素存在之前加入的map容器中
return new int[]{map.get(target - nums[i]), i};
}
// 不存在,则加入当前元素
map.put(nums[i], i);
}
// 未找到则返回空
return null;
}
}
2.只出现一次的数字
给定一个整数数组、只包含一个出现一次的数字、其余都是两次、找出出现一次的数字
/**
思路:使用hash表
存在,则value+1,不存在,则value=1
*/
public class Solution{
public int singletonValue(int[] nums){
Map<Integer, Integer> map = new HashMap<>();
// 遍历元素放入hash表中
for(Integer i : nums){
Integer count = map.get(i);
count = count == null ? 1 : ++count;
map.put(i. count);
}
// 找出value为1的值
for(Integer i : map.keySet()){
if(map.get(i) == 1){
return i;
}
}
return -1;
}
}
3.滑动窗口-不重复的最长字符串长度
/**
思路:
1、使用Map容器记录遍历后的元素、key=元素值-value=索引值
2、当出现重复元素时、则改变起始不重复元素的索引指针
3、通过right - left + 1计算最大长度
*/
public class Solution{
public int maxLength(String s){
// 获取长度
int length = s.length();
if(length == 0){
return 0;
}
// Hash结构,存储元素
HashMap<Character, Integer> map = new HashMap<>();
// 循环遍历元素
int max = 0;
int left = 0;
for(int i = 0; i < length; i++){
if(map.containsKey(s.charAt(i))){
// 包含重复元素。则改变起始位置
left = Math.max(left, map.get(s.charAt(i)) + 1);
}
// 不重复则加入元素
map.put(s.charAt(i), i);
// 计算最大长度
max = Math.max(max, i - left + 1);
}
return max;
}
}
3.递归
1.合并两个有序链表-21
指定两个递增的有序链表、将其从小到大进行合并,返回合并后的递增有序链表
/**
思路:
1、递归思路求解
2、第一次比较、list1中的1与list2中的2比较、
如果list1中的1结点 < 小于list2中的1结点,则比较list1中的2结点与list2中所有结
点。大于,则相反思想
3、当list1、list2中有一个为空则进行返回
*/
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
public class Solution{
public ListNode mergeListNode(ListNode list1, ListNode list2){
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
// 递归调用合并
if(list1.val < list2.val){
// 递归比较list1的下一个结点与list2中的所有结点
list1.next = mergeListNode(list1.next, list2);
return list1;
}else{
// 反过来进行递归
list2.next = mergeListNode(list2.next, list1);
return list2;
}
}
}
4.动态规划
1.最大子数组和
给定一个数组、求出最大值连续子数组,返回最大值
/**
思路:
关键词:连续、最大和、具有最大和的连续子数组
动态规划:大问题化为若干子问题,求最优解
1、如何划分子问题
第一种:我们无法确定最大和连续子数组中到底包含哪一个,但是可以知道经过每一个元素的最大和,然后在所有的最大和数组中找出最大值即可(最优解)
1.子问题1:经过-2的连续子数组最大和?
2.子问题2:经过1的连续子数组最大和?
......
9.子问题9:经过4的连续子数组最大和?
第二种:上述分析中,每个子问题中又包含不确定性,无法确定子问题中到底有多少中情况。因此、我们还要进行其他方式划分
1.子问题1:以-2结尾的连续子数组最大和?
2.子问题2:以1结尾的连续子数组最大和?
......
9.子问题9:以4结尾的连续子数组最大和?
2、如何确定状态转换
上述问题中,可能存在两种类型、全部大于0,与不全部大于0。这两种状态中,以nums[i]结尾的元素一定会被包含到,则只需要考虑剩余nums[i - 1]的状态。
1、如果全部或部分 > 0(且dp[i - 1] > 0),则dp[i] = dp[i - 1] + nums[s];
2、如果剩余部分 <= 0(且dp[i - 1] <= 0),则dp[i] = nums[i]
故状态转义方程如下:
dp[i - 1] + nums[i], dp[i - 1] > 0
dp[i] = {
nums[i], dp[i - 1] <= 0
*/
public class Solution{
public int maxSubArray(int[] nums){
// 定义状态数组
int len = nums.length;
int[] dp = new int[len];
// 初始化初值
dp[0] = nums[0];
// 根据状态转移方程计算子问题
for(int i = 1; i < len; i++){
if(dp[i - 1] > 0){
// dp[i - 1] > 0,直接相加
dp[i] = dp[i - 1] + nums[i];
}else{
// dp[i - 1] <= 0
dp[i] = nums[i];
}
}
// 从dp数组中寻找最大值
int result = dp[0];
for(int i = 1; i < len; i++){
result = Math.max(result, dp[i]);
}
return result;
}
}
2.爬楼梯
假设存在n阶楼梯、每次只能爬1或2个台阶。求出有多少中走法;
/**
思路:
关键词:共n阶、每次只能爬1或2阶。求走法?
动态规划,划分子问题、求子问题中的最优解
1、划分子问题
1.总共n阶楼梯
2.第n阶只有一种走法、则第n-1阶有多少种走法、第n-2阶有多少种走法
2、定义状态方程
0 1 2 3 5 走法
0 1 2 3 4 阶数
规律:从2开始,每一阶走法都是n-1 + n-2阶之和
dp[n] = dp[n - 1] + dp[n - 2]
*/
class Solution {
public int climbStairs(int n) {
if(n <= 2){
return n;
}
// 定义状态数组
int[] dp = new int[n + 1];
// 初始化初值
dp[0] = 0;
dp[1] = 1;
dp[2] = 2;
// 循环处理3阶以上问题
for(int i = 3; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
3.买卖股票最佳时机
给定一个股票数组、只能单次的买卖、当前买入、在未来的某一天卖出股票、求最大利润。
注意:不能在买入前进行卖出、不能在同一天进行买卖
/**
思路:动态规划
1、状态划分:
dp[i][0]:表示在[0 - i]区间范围内,第i天结束之后不持有股票
此时利润为0
dp[i][1]:表示在[0 - i]区间范围内,第i天结束之后持有股票
此时利润为第i天的负数 -precies[i]
2、状态转义方程
第一天:
dp[0][0]:不持股、则最大利润为0
dp[0][1]:持股、则最大利润为这一天的负数-prices[i]
第二天:
dp[1][0]:未持股、或者持股卖出(两者中间取最大值)
则最大利润为dp[i - 1][0], dp[i - 1][i] + prices[i];
dp[1][1]:持股则和之前的进行比较、取一个较大值
则最大利润为dp[i - 1][1], -prices[i]
*/
public class Solution{
public int maxPrice(int[] prices){
// 排除基本情况
int len = prices.length;
if(len < 2){
return 0;
}
// 定义状态方程
int[][] dp = new dp[lne][2];
// 初始化第一天的值
dp[0][0] = 0;
dp[0][1] = -prices[0];
// 从第二天开始进行遍历
for(int i = 1; i < len; i++){
// 不持股、或者持股卖出
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
}
// 返回最后一次最大值即可
return dp[len - 1][0];
}
}
4.最长回文子串
给定一个字符串、找出最长的回文字符串
/**
思路:动态规划求解最优解(最大值)
1、定义状态方程组
初始化一个二维数组、用来记录动态规划中的变化值
2、初始化化方程组
对角线元素中的自身都是回文字符串、可以初始化为true
3、状态转义方程
1、从第一个字符开始、逐渐扩宽字符区间、然后在从每个字符区间进行缩小
2、外层循环控制扩宽区间、内层循环控制缩减每个区间
false, chars[i] != chars[j]
dp[i][j] =
j-i < 3||dp[i+1][j-1]
*/
public class Solution{
public String maxString(String s){
// 获取字符串长度
int len = s.length();
if(len == 1){
return s;
}
// 定义状态方程
boolean[][] dp = new boolean[len][len];
// 获取字符数组
char[] chars = s.toCharArray();
// 初始化初始值
for(int i = 0; i < len; i++){
dp[i][i] = true;
}
// 定义最大长度、开始位置
int maxLen = 1;
int begin = 0;
// 循环开始遍历元素
for(int j = 1; j < len; j++){
for(int i = 0; i < j; i++){
// 判断当前区间中的两端是否相同
if(chars[i] != chars[j]){
dp[i][j] = false;
}else{
// 相同,则判断剩余个数是否小于3
if(j - i < 3){
dp[i][j] = true;
}else{
dp[i][j] = dp[i + 1][j - 1];
}
}
}
// 改变长度、开始位置
if(dp[i][j] && j - i + 1 > maxLen){
maxLen = j - i + 1;
begin = i;
}
}
// 返回最大回文子串
return s.substring(begin, begin + maxLen);
}
}