题目 | 难易程度 | 题号 |
---|---|---|
removeDuplicates | easy | 26 |
maxSubArray | easy | 53 |
reverseString | easy | 344 |
reverseVowels | easy | 345 |
addTwoNumbers | middle | 2 |
maxArea | middle | 11 |
permuteUnique | middle | 47 |
rotate | middle | 48 |
setZeroes | middle | 73 |
singleNumber | middle | 137 |
largestRectangleArea | hard | 84 |
github地址
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
下标索引i
从前遍历,指向该被赋值的位置,索引j
从i
的后面从前向后遍历,若j
指向的元素与i
相同,j
指向下一个索引,若不同执行赋值操作,将j
指向的值赋值到i
的位置,i
j
指向下一个索引,最后返回i+1
就是不重复的元素的个数
注:需要考虑边界情况
public static int removeDuplicates(int[] nums) {
if(nums.length == 0)return 0;
if(nums.length == 1)return 1;
int i = 0,j = 1;
while (j < nums.length && i < j){
if(nums[i] == nums[j]){
j++;
}else{
nums[++i] = nums[j];
j++;
}
}
return i+1;
}
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
自己对动态规划的理解也不是很好,在leetcode上找到了一个认为讲解比较清楚的题解,附上链接,最大子序和leetcode题解
题解的笔主对解决该动态规划的遍历方法提出了归纳,以当前节点为结束遍历数组。
看了下面的代码之后发现状态转移方程和打家劫舍的题目的方程是一样的。
public static int maxSubArray4(int[] nums) {
// Kadane算法扫描一次整个数列的所有数值,
// 在每一个扫描点计算以该点数值为结束点的子数列的最大和(正数和)。
// 该子数列由两部分组成:以前一个位置为结束点的最大子数列、该位置的数值。
// 因为该算法用到了“最佳子结构”(以每个位置为终点的最大子数列都是基于其前一位置的最大子数列计算得出,
// 该算法可看成动态规划的一个例子。
// 状态转移方程:sum[i] = max{sum[i-1]+a[i],a[i]}
// 其中(sum[i]记录以a[i]为子序列末端的最大序子列连续和)
// 在每一个扫描点计算以该点数值为结束点的子数列的最大和(正数和)。
int max_ending_here = nums[0];//以当前节点为结束的最大子序和
int max_so_far = nums[0];//最大子序he
for (int i = 1; i < nums.length; i ++ ) {
// 以每个位置为终点的最大子数列 都是基于其前一位置的最大子数列计算得出,
max_ending_here = Math.max ( nums[i], max_ending_here + nums[i]);//sum[i] = max{sum[i-1]+a[i],a[i]}
max_so_far = Math.max ( max_so_far, max_ending_here);
};
return max_so_far;
}
public static int maxSubArra3(int[] nums) {
int result = nums[0];
int sum = 0;
for(int i : nums){
if(sum + i > i )
// if(sum > 0)
sum += i;
else sum = i;
result = Math.max(result,sum);//result一直存储当前的最大子序和
}
return result;
}
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
public static void reverseString(char[] s) {
for(int i = 0,j = s.length - 1;i < j;i++,j--){
char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
编写一个函数,以字符串作为输入,反转该字符串中的元音字母
输入: "hello"
输出: "holle"
双指针分别从头尾遍历数组,找到应该进行交换的两个索引,进行值交换
public static boolean isVowel(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}
public static String reverseVowels(String s) {
int i = 0,j = s.length() - 1;
StringBuilder result = new StringBuilder(s);
while (i < j){
if(isVowel(s.charAt(i))){
if(isVowel(s.charAt(j))){
result.setCharAt(i,s.charAt(j));
result.setCharAt(j,s.charAt(i));
i++;j--;
}else{
j--;
}
}else{
i++;
if(!isVowel(s.charAt(j))){
j--;
}
}
}
return result.toString();
}
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
flag
表示是否存在进位,初始false
遍历两个链表,p
节点和q
节点
flag = true
时,若p.val + q.val + 1 >=10
赋值p.val = p.val + q.val + 1 - 10
,flag
不变;
若p.val + q.val + 1 <10
,p.val = p.val + q.val + 1
flag = false
时,计算方法类似,只不过不加1
最后遍历剩余的链表
public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// ListNode p = l1,q = l2;
ListNode result = new ListNode(l1.val + l2.val);
ListNode p = result;
Boolean forward = false;
if(result.val > 9){
result.val -= 10;
forward = true;
}
l1 = l1.next;l2 = l2.next;
while (l1 != null && l2 != null){
if(forward){
if(l1.val + l2.val + 1 >= 10){
p.next = new ListNode(l1.val + l2.val + 1 - 10);
forward = true;
}else{
p.next = new ListNode(l1.val + l2.val + 1);
forward = false;
}
}else{
if(l1.val + l2.val >= 10){
p.next = new ListNode(l1.val + l2.val - 10);
forward = true;
}else{
p.next = new ListNode(l1.val + l2.val);
forward = false;
}
}
l1 = l1.next;l2 = l2.next;p = p.next;
}
while (l1 != null){
if(forward){
if(l1.val + 1 >= 10){
l1.val = l1.val + 1 - 10;
forward = true;
}else{
l1.val += 1;
forward = false;
}
}
p.next = l1;
l1 = l1.next;p = p.next;
}
while (l2 != null){
if(forward){
if(l2.val + 1 >= 10){
l2.val = l2.val + 1 - 10;
forward = true;
}else{
l2.val += 1;
forward = false;
}
}
p.next = l2;
l2 = l2.next;p = p.next;
}
if(forward){
p.next = new ListNode(1);
}
return result;
}
给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
首先遍历数组,以某个元素为容器的一个边,遍历后面的元素,找到容器最大的边构成矩形
public class Solution {
public int maxArea(int[] height) {
int maxarea = 0;
for (int i = 0; i < height.length; i++)
for (int j = i + 1; j < height.length; j++)
maxarea = Math.max(maxarea, Math.min(height[i], height[j]) * (j - i));
return maxarea;
}
}
要想构成的容器盛水多要么容器底的面积大,要么容器高,对应的所选择的两个位置离得远,要么对应的数组数值大,首先从数组两边向中间遍历,容器的面积取决于较短的线段,若将指向较长边的索引向中间移动,容器的高度只能不变或者更低,而面积缩小,所以移动索引的条件就是线段短的索引向中间移动
public int maxArea(int[] height) {
int maxarea = 0, l = 0, r = height.length - 1;
while (l < r) {
maxarea = Math.max(maxarea, Math.min(height[l], height[r]) * (r - l));
if (height[l] < height[r])
l++;
else
r--;
}
return maxarea;
}
给定一个可包含重复数字的序列,返回所有不重复的全排列
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
题解
private static List<List<Integer>> res = new ArrayList<>();
private static boolean[] used;
private static void findPermuteUnique(int[] nums, int depth, Stack<Integer> stack){
if(depth == nums.length){
res.add(new ArrayList<>(stack));
return;
}
for(int i = 0; i < nums.length;i++){
if(!used[i]){
if(i > 0 && nums[i] == nums[i-1] && !used[i-1]){
continue;
}
used[i] = true;
stack.add(nums[i]);
findPermuteUnique(nums,depth + 1,stack);
stack.pop();
used[i] = false;
}
}
}
public static List<List<Integer>> permuteUnique(int[] nums) {
int len = nums.length;
if(len == 0)return res;
Arrays.sort(nums);
used = new boolean[len];
findPermuteUnique(nums,0,new Stack<>());
return res;
}
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
从外向内遍历矩阵,对于每一圈矩阵进行旋转的时候,上->右,右->下,下->左,左->上
public static void rotate(int[][] matrix) {
int n = matrix.length;
int rotate_num = n / 2;//一共旋转的圈数
int num = 0;//旋转的第几圈
while (rotate_num != 0){
num++;
int operate_num = n - 2 * (num - 1);//该圈一行或者一列操作的数据个数
int index1 = num - 1;//第一行
int index2 = n - 1 - (num - 1);//后面一行
ArrayList<Integer> tmp = new ArrayList<>();
for(int i = index1;i < index2;i++){
tmp.add(matrix[i][index2]);
matrix[i][index2] = matrix[index1][i];
}
for(int j = index1 + 1;j <= index2;j++){
int tmp1 = matrix[index2][j];
matrix[index2][j] = tmp.get(operate_num - 1 - (j - (index1 + 1)) - 1);
tmp.remove(operate_num - 1 - (j - (index1 + 1)) - 1);
tmp.add(operate_num - 1 - (j - (index1 + 1)) - 1,tmp1);
}
for(int i = index1 + 1;i <= index2;i++){
int tmp1 = matrix[i][index1];
matrix[i][index1] = tmp.get(operate_num - 1 - (i - (index1 + 1)) - 1);
tmp.remove(operate_num - 1 - (i - (index1 + 1)) - 1);
tmp.add(operate_num - 1 - (i - (index1 + 1)) - 1,tmp1);
}
for(int j = index1;j < index2;j++){
matrix[index1][j] = tmp.get(j - index1);
}
rotate_num--;
}
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
存储元素为0的下标,然后遍历下标数组,对应位置设置0
public static void setZeroes(int[][] matrix) {
List<Integer> row_index = new ArrayList<>();
List<Integer> column_index = new ArrayList<>();
for(int i = 0; i < matrix.length;i++){
for(int j = 0; j < matrix[0].length;j++){
if(matrix[i][j] == 0){
row_index.add(i);
column_index.add(j);
}
}
}
for(int i = 0;i < row_index.size();i++){
for(int j = 0; j < matrix[0].length;j++){
matrix[row_index.get(i)][j] = 0;
}
for(int k = 0;k < matrix.length;k ++ ){
matrix[k][column_index.get(i)] = 0;
}
}
}
遍历矩阵,若某个位置为0,将所在的行列的值都设置为一个不在规定范围内的数值标记,最后遍历矩阵,调整为0
解法二的标记方法可以节省额外的空间开销,但是存在双重标记的情况,可以遍历到为0的地方,将所在行列的第一个位置设置为0,这样可以保证被设置为0的位置不会被再次遍历到,矩阵遍历结束的时候将开头为0的行列设置为0
public static void setZeroes1(int[][] matrix) {
boolean row_flag = false;//行置0
boolean col_flag = false;//列置0
for(int i = 0; i < matrix.length;i++){
for(int j = 0; j < matrix[0].length;j++){
if(matrix[i][j] == 0){
matrix[0][j] = 0;
matrix[i][0] = 0;
if(i == 0)row_flag = true;
if(j == 0)col_flag = true;
}
}
}
for(int i = 1;i < matrix.length;i++){
if(matrix[i][0] == 0){
for(int j = 1;j < matrix[0].length;j++)
matrix[i][j] = 0;
}
}
for(int i = 1; i < matrix[0].length;i++){
if(matrix[0][i] == 0){
for(int j = 1;j < matrix.length;j++)
matrix[j][i] = 0;
}
}
if(matrix[0][0] == 0){
if(row_flag){
for(int j = 1;j < matrix[0].length;j++)
matrix[0][j] = 0;
}
if(col_flag){
for(int j = 1;j < matrix.length;j++)
matrix[j][0] = 0;
}
}
}
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:
输入: [0,1,0,1,0,1,99]
输出: 99
用map标记每个数字出现的次数,返回出现次数为1 的数值
public static int singleNumber(int[] nums) {
Map<Integer,Integer> map = new HashMap<>();
for(int i =0;i < nums.length;i++){
if(map.containsKey(nums[i])){
map.put(nums[i],-1);
}else{
map.put(nums[i],i);
}
}
for(Integer key:map.keySet()){
if(map.get(key) != -1){
return key;
}
}
return -1;
}
题解
public static int singleNumber1(int[] nums) {
int ans = 0;
for(int i = 0;i < 32;i++){
int count = 0; // 1出现的次数
for(int j = 0;j < nums.length;j++){
if((nums[j] >>> i & 1) == 1){
count ++;
}
}
if(count % 3 != 0){
ans = ans | 1 << i;
}
}
return ans;
}
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积
输入: [2,1,5,6,2,3]
输出: 10
矩形的最大面积受限于数组中最小的数值,如果以最小数值为分界线分别计算左侧和右侧的矩形的最大面积以及当前的面积,便可以输出最大的矩形
public static int largestRectangleArea2(int[] heights) {
return calculateArea(heights,0,heights.length - 1);
}
public static int calculateArea(int[] heights,int start,int end){
if(start >end)return 0;
int minindex = start;
for(int i = start; i <= end;i++){
if(heights[minindex] > heights[i])minindex = i;
}
return Math.max(heights[minindex] * (end - start + 1), Math.max(calculateArea(heights,start,minindex - 1),calculateArea(heights,minindex + 1,end)));
}