题目来源:LeetCode
文章目录
杨辉三角
1、第n行有n个数字
2、每一行的开始和结尾数字都为1
3、第n+1行的第i个数字等于第n行的i-1个数字加上第n行的i个数字
public class Main {
private void print(int[][] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j <= i; j++) {
System.out.print(arr[i][j]+"\t");
}
System.out.println();
}
}
private void rect(int rows){
int[][] arr = new int[rows][rows];
for (int i = 0; i < rows; i++) {
for (int j = 0; j <= i; j++) {
if (j==0 || i==j) {
arr[i][j] = 1;
}else {
arr[i][j] = arr[i-1][j-1] + arr[i-1][j];
}
}
}
print(arr);
}
public static void main(String[] args) {
Main main = new Main();
main.rect(10);
}
}
删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
示例 1: 给定数组 nums = [1,1,2], 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
示例 2: 给定 nums = [0,0,1,1,1,2,2,3,3,4],函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
解答: 由于已经排序,只需一次遍历,将数字往前移,后面部分即使乱序也不用管
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 1;
int max = nums[0];
int index = 1;
int temp = 0;
for (int i = 1; i < nums.length; i++) {
if (nums[i]>max){
max = nums[i];
temp = nums[i];
nums[i] = nums[index];
nums[index] = temp;
index ++;
}
}
return index;
}
}
最后一个单词的长度
给定一个仅包含大小写字母和空格 ’ ’ 的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。 如果不存在最后一个单词,请返回 0 。
说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。
示例: 输入: “Hello World” ,输出: 5
解答:从后往前,先过滤无用空格
注意: 最后有多个空格的情况
class Solution {
public int lengthOfLastWord(String s) {
int count = 0;
for (int i = s.length()-1; i >= 0; i--) {
if (s.charAt(i) == ' '){
if (count==0) continue;
break;
}else {
count ++;
}
}
return count;
}
}
加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 : 输入: [1,2,3] ,输出: [1,2,4],解释: 输入数组表示数字 123。
解答:从后往前,逢10进1
注意: 首位是9的情况
class Solution {
public int[] plusOne(int[] digits) {
for (int i = digits.length-1; i >= 0; i--) {
digits[i] = (digits[i]+1)%10;
if (digits[i] != 0) {
break;
}
}
if(digits[0] == 0){
//int[] arr = new int[digits.length+1];
//arr[0] = 1;
//System.arraycopy(arr,0,digits,0,digits.length);
//digits = arr;
digits = new int[digits.length+1];
digits[0] = 1;
}
return digits;
}
}
爬梯子
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。
示例 1: 输入: 2,输出: 2;解释: 有两种方法可以爬到楼顶。
示例 2: 输入: 3,输出: 3;解释: 有三种方法可以爬到楼顶。
解答: 爬到第 xx 级台阶的方案数是爬到第 x - 1x−1 级台阶的方案数和爬到第 x - 2x−2 级台阶的方案数的和:f(x)=f(x−1)+f(x−2)
class Solution {
public int climbStairs(int n) {
int p = 0;
int q = 1;
int res = 0;
for (int i = 0; i < n; i++) {
res = p + q;
p = q;
q = res;
}
return res;
}
}
合并两个有序数组
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:nums1 = [1,2,3,0,0,0], m = 3;nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
解答:三指针从后往前
注意:如[4,5,6,0,0,0]、[1,2,3]的情况
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1;
int p2 = n - 1;
int p = m + n - 1;
while (p1>=0 && p2>=0){
if (nums1[p1] >= nums2[p2]){
nums1[p] = nums1[p1];
p1 --;
}else {
nums1[p] = nums2[p2];
p2 --;
}
p --;
}
System.arraycopy(nums2,0,nums1,0,p2+1);
}
}
删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 : 输入: 1->1->2->3->3;输出: 1->2->3
解答:由于已排序,可以只考虑比较相邻节点的值
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode node = head;
while (node != null && node.next != null){
if (node.val == node.next.val){
node.next = node.next.next;
}else {
node = node.next;
}
}
return head;
}
}
解答成功:执行耗时:1 ms,击败了73.94% 的Java用户;内存消耗:39.5 MB,击败了42.35% 的Java用户
相同的树
给定两个二叉树,编写一个函数来检验它们是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 :
解答:
递归地判断两个二叉树是否相同。
1、如果两个二叉树都为空,则两个二叉树相同。
2、如果两个二叉树中有且只有一个为空,则两个二叉树一定不相同。
3、如果两个二叉树都不为空,那么首先判断它们的根节点的值是否相同,若不相同则两个二叉树一定不同,若相同,再分别判断两个二叉树的左子树是否相同以及右子树是否相同。
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p==null && q==null){
return true;
}else if (p==null || q==null){
return false;
}else if (p.val != q.val){
return false;
}else {
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
}
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户;内存消耗:37.3 MB,击败了39.50% 的Java用户
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
实例:二叉树 [1,2,2,3,4,4,3] 是对称的。
解答:
通过「同步移动」两个指针的方法来遍历这棵树,pp 指针和 qq 指针一开始都指向这棵树的根,随后 pp 右移时,qq 左移,pp 左移时,qq 右移。每次检查当前 pp 和 qq 节点的值是否相等,如果相等再判断左右子树是否对称。
class Solution {
public boolean check(TreeNode p, TreeNode q){
if (p==null && q==null) return true;
else if (p==null || q==null) return false;
else return p.val==q.val && check(p.left, q.right) && check(p.right, q.left);
}
public boolean isSymmetric(TreeNode root) {
return check(root, root);
}
}
解答成功: 执行耗时:1 ms,击败了29.43% 的Java用户 内存消耗:38.2 MB,击败了26.57% 的Java用户
二叉树的层次遍历-正/反序
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如 :
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其自底向上的层次遍历为: [[15, 7], [9, 20], [3]]
解答:
广度优先遍历,核心是用一个queue队列存节点信息。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
// 用于存最终结果
List<List<Integer>> ret = new ArrayList<>();
// 队列,用于存储节点信息
Queue<TreeNode> queue = new LinkedList<>();
if (root == null){
return ret;
}
// 先将head节点添加入队列,然后开始while循环。
queue.offer(root);
// 辅助作用的节点,用于指向出队的节点信息
TreeNode node;
while (!queue.isEmpty()){
// 临时的列表,存某一层的所有节点。
ArrayList<Integer> temp = new ArrayList<>();
// 得到队列的长度,此时队列的长度=这一层的节点数(后面可以发现)
int len = queue.size();
// 由于输出结果是二维数组结构,第二维的数组内数量就是该层的全部节点。
// 所以可以通过for循环填入某一层的所以节点。
for (int i = 0; i < len; i++) {
// 先出队第一个节点
node = queue.poll();
// 将该节点的值加入
temp.add(node.val);
// 如果左边子节点存在,则加入队列
if (node.left != null){
queue.offer(node.left);
}
// 如果右边子节点存在,则加入队列
if (node.right != null){
queue.offer(node.right);
}
}
// for循环完成,表示这一层的节点都已获取到,将此接入到结果列表中
ret.add(temp);
}
// 如果是倒序层次遍历,反转一下即可
Collections.reverse(ret);
return ret;
}
}
有序数组转换为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
例如 :
给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
解答:
class Solution {
public TreeNode tempFunc(int[] nums, int left, int right){
// left=right时,区间内只有一个元素
// left>right时,说明已没有元素,返回null节点
if (left > right) {
return null;
}
// 选择中间位置左边的元素作为根节点(因为整除:5/2=2)
int mid = (left + right)/2;
// 将元素的值作为中间节点
TreeNode root = new TreeNode(nums[mid]);
// 左子节点通过递归调用获得
// 返回的是中间节点左边区间里,中间元素的节点,让root.left指向它
// 递归里相当于不断把大区间二分成小区间
// 简单的情况如“1,2,3,4,5”,
// 首次进来1:left=0,mid=2,right=4;左边“1,2”,根节点“3”,右边“4,5”。
// left递归1:left=0,mid=0,right=1;左边“”,根节点“1”,右边“2”。
// left递归2:left=0,right=-1;不符合,返回null。
// right递归1:left=3,mid=3,right=4;左边“”,根节点“4”,右边“5”。
// right递归2:left=4,mid=4,right=4;左边“”,根节点“5”,右边“”。
// right递归3:left=5,right=4;不符合,返回null。
// 最终:(二叉搜索树的中序遍历是升序序列)
// 3
// / \
// 1 4
// \ \
// 2 5
root.left = tempFunc(nums, left, mid-1);
// 同上
root.right = tempFunc(nums, mid+1, right);
// 返回根节点(相对于当前节点而言是根节点)
return root;
}
public TreeNode sortedArrayToBST(int[] nums) {
TreeNode node = tempFunc(nums, 0, nums.length-1);
return node;
}
}
平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。 一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true
解答:
class Solution {
// 将大的树拆成若干个深度<=1的子树
// 每次判断当前父节点的左右子树的高度差是否>1
// 每次递归,如果不平衡,返回-1;如果平衡,返回子树的深度(1或0<空节点>)
// 叠加递归的结果,就是该父节点的深度
public int tempFunc(TreeNode root){
if (root == null){
return 0;
}
// 自底而上,先递归到最底层的左子树
int leftHeight = tempFunc(root.left);
// 左子树完,递归右子树
int rightHeight = tempFunc(root.right);
// 如果左右子树的高度差>1,则说明不平衡
// 如果左或右子树的高度=-1,则说明已经有子树不平衡了
if (leftHeight==-1 || rightHeight==-1 || Math.abs(leftHeight-rightHeight)>1){
return -1;
}
// 否则,一切正常,返回左右子树的最大值+1
else{
return Math.max(leftHeight, rightHeight) + 1;
}
}
// 返回结果如果=-1,则表示其中某一个节点的子树不平衡,则总的也不平衡
// 返回结果如果>=0,则表示全部平衡,则总的也平衡
public boolean isBalanced(TreeNode root) {
return tempFunc(root)>=0;
}
}
二叉树的最小深度
给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度: 2.
解答:
买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 注意:你不能在买入股票前卖出股票。
示例 : 输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
解答:
一次遍历,寻找最小值,作为买入点。若不是最小值,则比较利润是否最大。
class Solution {
public int maxProfit(int prices[]) {
// 预先设置一个最低价格
int minprice = Integer.MAX_VALUE;
// 预先设置利润=0
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
// 判断当前价格是否小于最低价格
if (prices[i] < minprice) {
// 更新最低价格
minprice = prices[i];
// 否则判断是否利润更大
} else if (prices[i] - minprice > maxprofit) {
// 更新最大利润
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
}
}
本来以为这种解法在“最低价格在后面的情况下会出错,如[7,6,5,3,1,4]”,后来想了想发现不会。因为是一次遍历,当找到最低价格后,前面的已经抛弃了,从之后再开始比较if(prices[i] - minprice > maxprofit)。
两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
解答:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode listNode = new ListNode(0);
ListNode node = listNode;
// 存放进位
int valH = 0;
// 只要有一边不为空,就循环相加
while (l1!=null && l2!=null) {
// 两数以及进位相加
int val = l1.val + l2.val + valH;
// 取当前和的进位,为下次用
valH = val/10;
// 取低位
int valL = val%10; // 15%10=5
// 创建节点
node.next = new ListNode(valL);
node = node.next;
l1 = l1.next;
l2 = l2.next;
}
// 若l1剩余,则全部添加进来
while (l1!=null) {
int val = l1.val + valH;
valH = val/10;
int valL = val%10; // 15%10=5
node.next = new ListNode(valL);
node = node.next;
l1 = l1.next;
}
// 若l2剩余,则全部添加进来
while (l2!=null) {
int val = l2.val + valH;
valH = val/10;
int valL = val%10; // 15%10=5
node.next = new ListNode(valL);
node = node.next;
l2 = l2.next;
}
// 若全部添加完还有进位,则额外添加一个“1”
if (valH==1) {
node.next = new ListNode(valH);
}
// 返回创建的根节点的next节点,因为根节点的val是无效的空值
return listNode.next;
}
无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
解答:
public int lengthOfLongestSubstring(String s) {
// 空字符串直接返回0
if (s.length() == 0) {
return 0;
}
// 存遍历过的字符,key=字符,value=该字符的下标+1(这样方便后面max()时候不用再判断)
Map<Character, Integer> map = new HashMap<>();
int maxLength = 0;
int start = 0;
int end = 0;
// 遍历字符串
for (end = 0; end < s.length(); end++) {
// 取出一个字符
char key = s.charAt(end);
// 判断是否在map中存在
// 1.若存在,且value>=start,说明当前序列中有重复了,start要移动到value
// 2.若存在,但value<start,说明这个存在的key不在当前序列中,start不用移动
// =>合并以上两种情况,不管>还是<,start都取他跟value的最大值即可
if (map.containsKey(key)) {
start = Math.max(start, map.get(key));
}
// 计算当前序列的长度,取最大值
maxLength = Math.max(maxLength, end-start+1);
// 将当前字符的下标放入map
// 注意1,下标+1:end+1
// 注意2,多次put相同的key时,value会被覆盖
map.put(key, end+1);
}
return maxLength;
}
盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
**说明:**你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
// 在初始时,左右指针分别指向数组的左右两端,它们可以容纳的水量为 min(1, 7) * 8 = 8
// 此时我们需要移动一个指针。直觉告诉我们,应该移动对应数字较小的那个指针(即此时的左指针)。
// 如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,
// 后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。
// 因此,我们移动 数字较小的那个指针。
// 所以,我们将左指针向右移动:
// 此时可以容纳的水量为 min(8,7)∗7=49。由于右指针对应的数字较小,我们移动右指针
// 此时可以容纳的水量为 min(8,3)∗6=18。
// 重复,直到两个指针重合。
public int maxArea(int[] height) {
// 左指针
int l = 0;
// 右指针
int r = height.length-1;
// 存最大值
int max = 0;
// 循环执行,直至左右指针重合
while (l<r) {
// 以短板为准,计算一下容量
int temp = Math.min(height[l], height[r]) * (r-l);
// 比较最大值
max = Math.max(temp, max);
// 移动更小的一边
if (height[l]<height[r]) {
l++;
}else {
r--;
}
}
System.out.println(max);
return max;
}