给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
采用暴力破解 时间复杂度O(n^2)
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i = 0; i < nums.length-1; i++){
for(int j = i + 1; j < nums.length; j++){
if(nums[j] == target - nums[i]){
return new int[]{i,j};
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
使用HashMap 时间复杂度O(n)
class Solution {
public int[] twoSum(int[] nums, int target) {
Map map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
//注意点1 先判断后put()
if(map.containsKey(target - nums[i])){
return new int[]{i,map.get(target - nums[i])};
}
//注意点2 把nums[i]当键,把i当做值
map.put(nums[i],i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
数组是内存中一串连续的内存地址,有一个内存管理器,可以随机访问任何一个数组下标的内存元素
Access: O(1)
Insert:平均O(n)
Delete:平均O(n)
由于数组不适合插入/删除,链表应运而生
Access: O(1)
Insert:平均O(n)
Delete:平均O(n)
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
使用迭代
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head;//第一次为1
ListNode prev = null;//null
while(cur != null){
ListNode second = cur.next;//把当前节点的下一个节点存在second
cur.next = prev;//
prev = cur;//
cur = second;//把second给当前节点
}
return prev;
}
}
使用递归
class Solution {
public ListNode reverseList(ListNode head) {
//处理最小输入的情况,即空链表和单节点链表
if (head == null || head.next == null) {
return head;
}
ListNode second = head.next;
ListNode reverseHead = reverseList(second);
second.next = head;
head.next = null;
return reverseHead;
}
}
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode h = new ListNode(-1);
h.next = head;
ListNode pre = h;
while (pre.next != null && pre.next.next != null){
ListNode node1 = pre.next;
ListNode node2 = node1.next;
ListNode lat = node2.next;
pre.next = node2;
node2.next = node1;
node1.next = lat;
pre = node1;
}
return h.next;
}
}
给定一个链表,判断链表中是否有环。
进阶:
你能否不使用额外空间解决此题?
思路:1、暴力破解,定时0.5秒,1秒 判断节点最后是否为null
2、用set数据结构存储节点,像狗一样留下气味,判断节点有没有重复,有重复则有环 O(n)时间复杂度
3、快慢指针,龟兔赛跑
龟兔赛跑
public class Solution {
public boolean hasCycle(ListNode head) {
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;
}
}
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
思路1、左括号(包含大中小)–> push()
2、右括号(包含大中小)–> 先peek(),再pop()
3、判断栈isEmpty()
class Solution {
private Map map = new HashMap<>();
public Solution(){
map.put(')','(');
map.put(']','[');
map.put('}','{');
}
public boolean isValid(String s) {
Stack stack = new Stack();
//将字符串 拆分成多个字符
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
if(map.containsKey(c)){
//字符是 右类型的括号 如果这是的栈里为空,就是不合法
if(stack.isEmpty()){
return false;
}
//把栈的顶部元素拿出来,把它与 现在的右类型括号进行对比
char topElement= stack.peek();
if(map.get(c) == topElement){
stack.pop();
continue;
}
return false;
}else{
//字符是 左类型的括号 压入栈
stack.push(c);
}
}
return stack.isEmpty();
}
}
class MyQueue {
private Stack stack1;
private Stack stack2;
/** Initialize your data structure here. */
public MyQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
stack1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
class MyStack {
private LinkedList q1;
private LinkedList q2;
/** Initialize your data structure here. */
public MyStack() {
q1 = new LinkedList<>();
q2 = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
q1.add(x);
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
if( q1.size() == 1){
return q1.poll();
}else{
while(q1.size() > 1){
q2.add(q1.poll());
}
while(q2.size() > 0){
q1.add(q2.poll());
}
return q1.poll();
}
}
/** Get the top element. */
public int top() {
if(q1.isEmpty()){
while(!q2.isEmpty()){
q1.add(q2.poll());
}
}
if(q1.size() == 1){
return q1.peek();
}else{
while(q1.size() > 1){
q2.add(q1.poll());
}
// while(q2.size() > 0){
// q1.add(q2.poll());
// }
return q1.peek();
}
}
/** Returns whether the stack is empty. */
public boolean empty() {
return q1.isEmpty() && q2.isEmpty();
}
}
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
思路: 1、筛选出前k大的数字,每一次都对它们进行排序,时间复杂度是O(N*klogk)
2、使用优先队列(小顶堆,即小顶堆的顶部永远是最小的),保证小顶堆的size = k ,时间复杂度最好O(1),最差O(log2K),优于Klogk,快了10倍。
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue q = new PriorityQueue<>(k);
for(int i = 0; i < nums.length; i++){
if(q.size() < k){
q.offer(nums[i]);
}else if(q.peek() < nums[i]){
q.poll();
q.offer(nums[i]);
}
}
return q.peek();
}
}
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。
返回滑动窗口最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
注意:
你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。
进阶:
你能在线性时间复杂度内解决此题吗?
思路:
1、使用优先队列,最大堆维护k个数据,每次维护都得排序 时间复杂度O(N*logk)
2、使用队列(双端队列),维护k个数据,时间复杂度O(N)
class Tmp{
public Tmp() {
}
public Tmp(Integer num,Integer pos) {
this.pos = pos;
this.num = num;
}
public Integer pos;
public Integer num;
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public Integer getPos() {
return pos;
}
public void setPos(Integer pos) {
this.pos = pos;
}
}
class Solution {
public ArrayList maxSlidingWindow(int[] nums, int k) {
ArrayList arr = new ArrayList<>();
PriorityQueue q = new PriorityQueue(k, new Comparator() {
@Override
public int compare(Tmp o1, Tmp o2) {
return o2.num - o1.num;
}
});
for(int i = 0; i < nums.length; i++){
if(q.size() < k){
q.offer(new Tmp(nums[i],i));
}else{
q.offer(new Tmp(nums[i],i));
Tmp p = q.peek();
while(p.getPos() < i-(k-1)){
q.poll();
p = q.peek();
}
arr.add(p.getNum());
}
}
return arr;
}
}
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:
输入: s = “rat”, t = “car”
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
思路:
使用排序,按字母字典顺序排序,时间复杂度(Klogk),比较排完顺序后的值
使用Map,统计出每个字符出现的次数{ letter,count}, for循环复杂度N Map每次插入O(1),总体复杂度O(N)
class Solution {
public boolean isAnagram(String s, String t) {
if(s.length() != t.length()){
return false;
}
int[] sArray = new int[26];
int[] tArray = new int[26];
for (int i = 0; i < s.length(); i++) {
//charAt(i) 就是在第i个位置的字符
sArray[s.charAt(i)-97] ++;
tArray[t.charAt(i)-97] ++;
}
for (int i = 0; i < 26; i++)
if (sArray[i]!=tArray[i]){
return false;
}
return true;
}
}
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode root, long minVal, long maxVal) {
if (root == null) return true;
if (root.val >= maxVal || root.val <= minVal) return false;
return isValidBST(root.left, minVal, root.val) && isValidBST(root.right, root.val, maxVal);
}
}
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
_______3______
/ \
___5__ ___1__
/ \ / \
6 _2 0 8
/ \
7 4
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
递归法
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
return left == null ? right : right == null ? left : root ;
}
}
循环法 二叉搜索树的最近公共祖先
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if( p.val < root.val && root.val > q.val){
return lowestCommonAncestor(root.left,p,q);
}
if( p.val > root.val && root.val < q.val){
return lowestCommonAncestor(root.right,p,q);
}
return root;
}
}