【11.1】包含min函数的栈
分析:栈本质上也是一个链表。pop就是找到当前头部节点的上一个节点。
要保证min()函数的时间复杂度为O(1),可用辅助栈实现。
`class MinStack {
Stack A, B;
//定义两个栈,A和B;
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
//假如A现在push的元素小于B的顶点节点元素,则push到B栈中。
A.add(x);
if(B.empty() || B.peek() >= x)
B.add(x);
}
public void pop() {
//假如A弹出的元素等于B的顶点节点,则为了保持两个栈的一致性,弹出B的顶点节点
if(A.pop().equals(B.peek()))
B.pop();
}
public int top() {
return A.peek();
}
public int min() {
return B.peek();
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/solution/mian-shi-ti-30-bao-han-minhan-shu-de-zhan-fu-zhu-z/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
`
class MinStack {
private Node head;
/** initialize your data structure here. */
public MinStack() {
}
public void push(int x) {
if(head == null)
head = new Node(x,x,null);
//第一个x为节点的value,第二个x保存节点的最小值;第三个null为该节点的下一个节点
else
head = new Node(x,Math.min(head.min,x),head);
}
public void pop() {
head = head.next;
}
public int top() {
return head.val;
}
public int min() {
return head.min;
}
private class Node{
int val;
int min;
Node next;
public Node(int val,int min,Node next){
this.val = val;
this.min = min;
this.next = next;
}
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.min();
*/
【11.2】
第一题:圆圈中最后剩下的数字(约瑟夫环)
分析:
1.循环链表实现:节点每走三步,就删掉当时位置的结点;
优点是简单易懂,缺点是模拟整个游戏过程的时间复杂度太高。
2.公式法:
按照上图显示的,上一轮和下一轮的坐标差3,越界了的话就循环(%总人数)。
因此,有递归函数:f(n,m) = (f(n-1,m)+m) % n,这样算出来的就是胜利者的下标位置。其中n为总人数。
class Solution {
public int lastRemaining(int n, int m) {
int p = 0;
for(int i = 2;i <= n;i++){
p = (p + m) % i;
}
return p;
}
}
第二题:【和为s的两个数字】
分析:设置双哨兵i,j,i指向最小值,j指向不大于target的最大值,假如i+j > target,则j向左边走,否则i向右边走;
//执行用时:2 ms, 在所有 Java 提交中击败了98.40% 的用户
//内存消耗:55.6 MB, 在所有 Java 提交中击败了54.09% 的用户
class Solution {
public int[] twoSum(int[] nums, int target) {
int i = 0,j = nums.length-1;
int[] arr = new int[2];
while(i < j){
while(nums[j] > target){
j--;
}
if(nums[i] + nums[j] < target){
i++;
}
else if(nums[i] + nums[j] > target){
j--;
}
else{
arr[0] = nums[i];
arr[1] = nums[j];
return arr;
}
}
return null;
}
}
【11.3】
第一题:两个数组的交集
分析:最直观的求交集就是两个for循环嵌套查找,这样时间复杂度是O(mn)。
第一种方法:哈希集合存储元素,则可用在O(1)的时间内判断一个元素是否在集合中,从而降低时间复杂度。
第二种方法:先排序,然后双指针。
//第一种方法:
//执行用时:3 ms, 在所有 Java 提交中击败了95.77% 的用户
//内存消耗:38.5 MB, 在所有 Java 提交中击败了93.55% 的用户
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
//首先定义两个集合
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new HashSet<Integer>();
//遍历数组,将数组元素放到集合中
for(int num:nums1){
set1.add(num);
}
for(int num:nums2){
set2.add(num);
}
return getIntersection(set1,set2);
}
public int[] getIntersection(Set<Integer> set1,Set<Integer> set2){
//选小的集合
if(set1.size() > set2.size()){
return getIntersection(set2,set1);
}
//用哈希表集合作为答案集合
Set<Integer> intersectionSet = new HashSet<Integer>();
for(int num:set1){
if(set2.contains(num)){
intersectionSet.add(num);
}
}
int[] intersection = new int[intersectionSet.size()];
int index = 0;
for(int num:intersectionSet){
intersection[index++] = num;
}
return intersection;
}
}
//第二种方法:
//执行用时:1 ms, 在所有 Java 提交中击败了99.95% 的用户
//内存消耗:38.8 MB, 在所有 Java 提交中击败了76.40% 的用户
class Solution{
public int[] intersection(int[] nums1,int[] nums2){
Arrays.sort(nums1);
Arrays.sort(nums2);
int length1 = nums1.length,length2 = nums2.length;
int[] intersection = new int[length1 + length2];
int index = 0,index1 = 0,index2 = 0;
while(index1 < length1 && index2 < length2){
int num1 = nums1[index1],num2 = nums2[index2];
if(num1 == num2){
if(index == 0 || num1 != intersection[index -1]){
intersection[index++] = num1;
}
index1++;
index2++;
}else if(num1 < num2){
index1++;
}else{
index2++;
}
}
return Arrays.copyOfRange(intersection,0,index);
}
}
第二题:和为s的连续正数序列
分析:由于是连续子序列,那么就有范围(也就是说一个上界,一个下界),那么最直观的就是一个一个暴力查询。例如以1为下界,累加2,累加3,假如累加和小于target那么继续累加,假如大于target,则退出,改变下界,只有当刚好等于target才停止。
如何优化?(优化可从两个方面来着手,一是改变计算的方式,例如说如何累加速度最快,公式、框架、模型都可以加快速度;二是减少计算量,先筛选掉/剪枝不可能的情况,再累加。)
- 累加——累加公式;
- 筛选——满足条件;
假设上界为y,下界为x,那么累加和为(x+y)(y-x+1)/2 = target
展开公式:y2+y−x2+x−2∗target=0,要使方程有整数解,需要满足两个条件
- 判别式 b^2-4ac 开根需要为整数
- 最后的求根公式的分子需要为偶数,因为分母为 2
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> vec = new ArrayList<int[]>();
int sum = 0, limit = (target - 1) / 2; // (target - 1) / 2 等效于 target / 2 下取整
for (int x = 1; x <= limit; ++x) {
long delta = 1 - 4 * (x - (long) x * x - 2 * target);
if (delta < 0) {
continue;
}
int delta_sqrt = (int) Math.sqrt(delta + 0.5);
if ((long) delta_sqrt * delta_sqrt == delta && (delta_sqrt - 1) % 2 == 0) {
int y = (-1 + delta_sqrt) / 2; // 另一个解(-1-delta_sqrt)/2必然小于0,不用考虑
if (x < y) {
int[] res = new int[y - x + 1];
for (int i = x; i <= y; ++i) {
res[i - x] = i;
}
vec.add(res);
}
}
}
return vec.toArray(new int[vec.size()][]);
}
}
【11.4】二进制中1的个数
分析:首先将数字转化为二进制,然后遍历二进制的每一位,统计1的个数。
难点在于:
Q1.如何转化进制?Q2.如何确定二进制位的长度然后遍历统计?
A1:在存储器里存的就是二进制位…
A2:进行&1操作,判断当前二进制位的末尾是否为1,然后右移一位操作,再次进行判断,假如&的结果为1,则计数器+1,最后返回计数器的结果即可。
这里出错是优先级问题,恒等运算符优先级比&要大。
//执行用时:1 ms, 在所有 Java 提交中击败了99.36% 的用户
//内存消耗:35.3 MB, 在所有 Java 提交中击败了93.08% 的用户
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
return res;
}
}
第二题:链表中倒数第k个节点
分析:之前考研王道书上有这么个题。
第一种方法:说到倒数——栈;
第二种方法:设置两个哨兵,两者步长为k(先让j往前走k步),当j到达终点时,i所在的位置就是倒数第k个节点。
//执行用时:4 ms, 在所有 C 提交中击败了51.01% 的用户
//内存消耗:5.9 MB, 在所有 C 提交中击败了5.27% 的用户
struct ListNode* getKthFromEnd(struct ListNode* head, int k){
struct ListNode* i = head;
struct ListNode* j = head;
if(head = NULL || k == 0){
return NULL;
}
while(k--){
j = j->next;
}
while(j!= NULL){
i = i->next;
j = j->next;
}
return i;
}
//执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
//内存消耗:36.3 MB, 在所有 Java 提交中击败了91.64% 的用户
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode i = head,j = head;
for(int a = 0;a < k;a++){
j = j.next;
}
while(j != null){
i = i.next;
j = j.next;
}
return i;
}
}
【11.5】打印从1到最大的n位数
分析:主要就是如何表达这个“位数”。假如是最大的三位数:10的三次方-1即可。
但是问题又来了,pow函数结果是double类型,因此要强转成int
//执行用时:1 ms, 在所有 Java 提交中击败了99.99% 的用户
//内存消耗:46.9 MB, 在所有 Java 提交中击败了22.27% 的用户
class Solution {
public int[] printNumbers(int n) {
int end = (int)Math.pow(10,n) - 1;
int[] arr = new int[end];
for(int i = 1;i <= end;i++){
arr[i-1] = i;
}
return arr;
}
}
(大数问题)下面是考虑当n很大的时候,打印出来的数超过INT_MAX范围的情况。
对于大数问题,变量类型用字符串String类型存储。且本题的字符集实际上是n位0-9的全排列,因此可避开进位操作,直接递归生成String列表。
有个疑惑的问题:假如从0开始的话,会不会输出的不是个位数1,2,而是01,02这种?