热题系列9

剑指 Offer 39. 数组中出现次数超过一半的数字

给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
数据范围:0≤n≤50000,数组中元素的值0≤val≤10000
要求:空间复杂度O(1),时间复杂度O(n)
输入描述:
保证数组输入非空,且保证有解

示例1
输入:[1,2,3,2,2,2,5,4,2]
返回值:2

示例2
输入:[3,3,3,3,2,2,2]
返回值:3

示例3
输入:[1]
返回值:1

题解一:

1.用一个dict记录nums数组中每个数字出现的次数。

2.返回dict中出现次数大于数组长度一半的数字。

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        length = len(nums)
        numsdict = {}
        for num in nums:
            if num not in numsdict:
                numsdict[num]=1
            else:
                numsdict[num]=numsdict[num]+1
        for num in numsdict:
            if numsdict[num]>length/2:
                return num

题解二:因为寻找的数字出现次数大于数组的一半,因此该数位于排序后数组的中间位置

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        new = sorted(nums)
        length = len(nums)
        return new[length//2]

678. 有效的括号字符串

在这里插入图片描述
如果没有*:用一个变量,遇到左括号,自增1,遇到右括号,如果此时计数器已经为0了,直接返回false,否则自减1,一旦计数器出现了负数,立即返回false,最后还要看变量是否为0即可
难点是这里有*, 它既可以作为(,又可以作为)。问题是它什么时候当做括号来用呢?举个极端例子:
):此时作为(
):此时当左括号,右括号,还是空,( 都是不对的
总之:只要
在)的前面,就一定可以消除)
算法:
用两个stack,分别存放(和的位置
遍历字符串:
当遇到
时,压入
当遇到(时,压入(栈
当遇到)时:
如果leftStack和starStack均为空,返回false
如果leftStack不为空,则pop出一个(来抵消当前),否则从starStack中pop出一个
来当做(来抵消)
遍历结束后,我们希望leftStack中没有多余的(;如果有,我们可以尝试用*来抵消它:
当star和left均不为空时,进行循环:
如果left的栈顶左括号的位置在star的栈顶星号的右边,那么就组成了 *( 模式,直接返回false
否则就说明星号可以抵消左括号,各自pop一个元素。
最终退出循环后我们看left中是否还有多余的左括号,没有就返回true,否则false

class Solution {
public:
    bool checkValidString(string s) {
        std::stack<int> left, star;
        for (int i = 0; i < s.size(); ++i) {
            if(s[i] == '*'){
                star.push(i);
            }else if(s[i] == '('){
                left.push(i);
            }else{
                if(left.empty() && star.empty()){
                    return false;
                }
                if(!left.empty()){
                    left.pop();
                }else{
                    star.pop();
                }
            }
        }
        
        while (!left.empty() && !star.empty()){
            if(left.top() > star.top()){
                return false;
            }
            left.pop();
            star.pop();
        }
        return left.empty();
    }
};

正向把所有星号转成左括号,反向把所有星号转成右括号,统计左括号和右括号的数量,只要有一个小于0,说明不匹配,也就不是一个有效字符串

class Solution {
public:
    bool checkValidString(string s) {
        return helper(s, 0, 0);
    }
    bool helper(string s, int start, int cnt) {
        if (cnt < 0) return false;
        for (int i = start; i < s.size(); ++i) {
            if (s[i] == '(') {
                ++cnt;
            } else if (s[i] == ')') {
                if (cnt <= 0) return false;
                --cnt;
            } else {
                return helper(s, i + 1, cnt) || helper(s, i + 1, cnt + 1) || helper(s, i + 1, cnt - 1);
            }
        }
        return cnt == 0;
    }
};

面试题 02.05. 链表求和

题目:给定两个用链表表示的整数,每一个节点包含一个数位。这些数式反向存放的,也就是个位排在链表首部,编写函数对这两个整数求和,并用链表形式返回结果。

示例:
输入:(7->1->6)和(5->9->2)
即617+295
输出:2->1->9 即912

struct ListNode
{
	int val;
	ListNode* next;
	ListNode(int x):val(x),next(NULL){}
};
class Solution
{
public:
	ListNode*  TwoList_Sum(ListNode* l1,ListNode* l2)
	{
		ListNode* head=new ListNode(-1);
		ListNode* p=head;
		ListNode* p1=l1;
		ListNode* p2=l2;
		int sum=0;
		int flag=0;
		while(p1 || p2 || flag)
		{
			sum=0;
			if(p1)
			{
				sum+=p1->val;
				p1=p1->next;
			}
			if(p2)
			{
				sum+=p2->val;
				p2=p2->next;
			}
			sum+=flag;
			ListNode* temp=new ListNode(sum % 10);
			p->next=temp;
			flag=sum / 10;
			p=p->next;
		}
		return head->next;
	}
};

71. 简化路径

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为更加简洁的规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,‘//’)都被视为单个斜杠 ‘/’ 。 对于此问题,任何其他格式的点(例如,‘…’)均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
始终以斜杠 ‘/’ 开头。
两个目录名之间必须只有一个斜杠 ‘/’ 。
最后一个目录名(如果存在)不能 以 ‘/’ 结尾。
此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 ‘.’ 或 ‘…’)。
返回简化后得到的 规范路径 。

示例 1:
输入:path = “/home/”
输出:“/home”
解释:注意,最后一个目录名后面没有斜杠。

示例 2:
输入:path = “/…/”
输出:“/”
解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。

示例 3:
输入:path = “/home//foo/”
输出:“/home/foo”
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例 4:
输入:path = “/a/./b/…/…/c/”
输出:“/c”
提示:

1 <= path.length <= 3000
path 由英文字母,数字,‘.’,‘/’ 或 ‘_’ 组成。
path 是一个有效的 Unix 风格绝对路径。

解题思路
利用栈。如果是…且栈里有元素,则弹出栈顶。如果是其他的则压栈,Python可以直接利用split去除掉多余的/,所以不用考虑

class Solution:
    def simplifyPath(self, path: str) -> str:
        path = path.split('/')
        stack = []
        for item in path:
            if item == '':
                continue
            if item == '..':
                if len(stack) > 0:
                    stack.pop()
            elif item != '.':
                stack.append(item)
        return "/"+'/'.join(stack)

679. 24 点游戏

你有 4 张写有 1 到 9 数字的牌。你需要判断是否能通过 *,/,+,-,(,) 的运算得到 24。
示例 1:
输入: [4, 1, 8, 7]
输出: True
解释: (8-4) * (7-1) = 24

示例 2:
输入: [1, 2, 1, 2]
输出: False

注意:
除法运算符 / 表示实数除法,而不是整数除法。例如 4 / (1 - 2/3) = 12 。
每个运算符对两个数进行运算。特别是我们不能用 - 作为一元运算符。例如,[1, 1, 1, 1] 作为输入时,表达式 -1 - 1 - 1 - 1 是不允许的。
你不能将数字连接在一起。例如,输入为 [1, 2, 1, 2] 时,不能写成 12 + 12 。

思路:回溯
抛开本题,先说一下,我们平时玩 24 点游戏一般顺序是:
选择两个数字,进行四项运算;
然后就计算结果与剩下两个数字中,再次选取两个数字,再进行四项运算,得到结果;
最终剩下两个数字,进行四项运算,看最终结果是否等于 24。
现在看本题,我们也是采用上面的形式进行枚举,去尝试所有可能的组合。
这里额外提及一下,在例题当中出现了括号’(’、’)’,这里括号有优先级。但是在这里,我们不做处理,因为在枚举的时候,是包含这种情况的。
还有个需要注意的地方。题目中说明除法运算是实数除法,并不是整数除法,那么这里会涉及到精度问题。这里默认只要最终结果误差小于 1e-6,那么认为相等。除数不能为 0,这里也同样默认绝对值小于 1e-6,则认为等于 0。
我们知道,四项运算中,加法、乘法是符合交换律的。那么这两项运算当中,两个数字前后位置不影响结果,可考虑跳过其中一种情况。

class Solution:
    def judgePoint24(self, nums: List[int]) -> bool:
        def helper(nums):
            if not nums:
                return False
            if len(nums) == 1:
                return abs(nums[0] - 24) < 0.000001

            for i in range(len(nums)):
                for j in range(len(nums)):
                    # 不能选取同个位置的数字
                    if i == j:
                        continue
                    # 标记是否最终结果等于 24
                    status = False

                    x = nums[i]
                    y = nums[j]

                    # 选取 i,j 对应位置的数字进行计算,其他数字放入数组后续计算
                    new_nums = [nums[k] for k in range(len(nums)) if( k!=i and k!=j)]
                    # 执行四项运算,加法乘法符合交换律,a + b = b + a, a*b=b*a,可跳过其中一项
                    # 这里默认 i > j 部分跳过,or 短路语句
                    # 加法,乘法
                    if i < j:
                        status = status or helper(new_nums + [x+y])
                        status = status or helper(new_nums + [x*y])
                    # 减法
                    status = status or helper(new_nums + [x-y])
                    # 除法
                    if abs(y) > 0.000001:
                        status = status or helper(new_nums + [x/y])

                    if status:
                        return True
            return False
        return helper(nums)

44. 通配符匹配

给你一个输入字符串 (s) 和一个字符模式 § ,请你实现一个支持 ‘?’ 和 ‘’ 匹配规则的通配符匹配:
‘?’ 可以匹配任何单个字符。
'’ 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

示例 1:
输入:s = “aa”, p = “a”
输出:false
解释:“a” 无法匹配 “aa” 整个字符串。

示例 2:
输入:s = “aa”, p = “”
输出:true
解释:'’ 可以匹配任意字符串。

示例 3:
输入:s = “cb”, p = “?a”
输出:false
解释:‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。

方法一:动态规划
解题步骤
初始化状态数组:创建一个二维布尔数组 ( dp ) ,其中 ( dp[i][j] ) 表示字符串 ( s ) 的前 ( i ) 个字符是否与模式 ( p ) 的前 ( j ) 个字符匹配。
处理边界条件:
( dp[0][0] = \text{True} ),两个空字符串是可以匹配的。
( dp[i][0] = \text{False} ),空模式不能匹配非空字符串。
( dp[0][j] ),只有当 ( p ) 的前 ( j ) 个字符全部为 ‘’ 时才为真。
填充状态数组:
如果 ( p[j-1] == ‘?’ ) 或 ( s[i-1] == p[j-1] ),则 ( dp[i][j] = dp[i-1][j-1] )。
如果 ( p[j-1] == ‘’ ),则 ( dp[i][j] = dp[i][j-1] )(不使用 '’) 或 ( dp[i-1][j] )(使用 ‘
’)。
返回结果:( dp[len(s)][len§] )。

def isMatch(s: str, p: str) -> bool:
    m, n = len(s), len(p)
    dp = [[False] * (n + 1) for _ in range(m + 1)]
    dp[0][0] = True

    for j in range(1, n + 1):
        if p[j - 1] == '*':
            dp[0][j] = dp[0][j - 1]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if p[j - 1] == '?' or s[i - 1] == p[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
            elif p[j - 1] == '*':
                dp[i][j] = dp[i][j - 1] or dp[i - 1][j]

    return dp[m][n]

# 示例调用
print(isMatch("aa", "a"))  # 输出: False
print(isMatch("aa", "*"))  # 输出: True

法二:双指针
解题步骤
初始化指针:分别为字符串 ( s ) 和模式 ( p ) 设置两个指针 ( i ) 和 ( j )。
跟踪最后一个星号:使用变量记录最后匹配的 ‘’ 的位置和对应的 ( s ) 的指针位置。
匹配字符串与模式:
如果当前字符匹配或 ( p[j] == ‘?’ ),则 ( i ) 和 ( j ) 都加一。
如果 ( p[j] == ‘
’ ),更新星号匹配的位置,将 ( j ) 增加一,并记录 ( i ) 的位置。
如果不匹配,检查是否存在之前匹配的 ‘',如果有,回溯到最后一个 '’,增加对应的 ( s ) 的指针位置。
检查剩余模式字符:所有主要字符匹配后,确保模式剩余的都是 ‘*’。

def isMatch(s: str, p: str) -> bool:
    si, pi = 0, 0
    star_idx = -1
    match = 0
    while si < len(s):
        if pi < len(p) and (s[si] == p[pi] or p[pi] == '?'):
            si += 1
            pi += 1
        elif pi < len(p) and p[pi] == '*':
            star_idx = pi
            match = si
            pi += 1
        elif star_idx != -1:
            pi = star_idx + 1
            match += 1
            si = match
        else:
            return False

    while pi < len(p) and p[pi] == '*':
        pi += 1

    return pi == len(p)

# 示例调用
print(isMatch("aa", "a"))  # 输出: False
print(isMatch("aa", "*"))  # 输出: True

168. Excel表列名称

题目描述:
给定一个正整数,返回它在 Excel 表中相对应的列名称。
例如,

1 -> A
2 -> B
3 -> C
...
26 -> Z
27 -> AA
28 -> AB 
...

示例 1:

输入: 1
输出: “A”
示例 2:

输入: 28
输出: “AB”
示例 3:

输入: 701
输出: “ZY”

解题思路:
Excel序是这样的:A~Z, AA~ZZ, AAA~ZZZ, ……
本质上就是将一个10进制数转换为一个26进制的数
注意:由于下标从1开始而不是从0开始,因此要减一操作。

class Solution(object):
    def convertToTitle(self, n):
        """
        :type n: int
        :rtype: str
        """
        res=''
        alp='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        while n>0:
            i=(n-1)%26
            res+=alp[i]
            n=(n-i)//26
        return res[::-1]

剑指 Offer 61. 扑克牌中的顺子

从扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这 5 张牌是不是连续的。2 ~ 10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
数组长度为 5
数组的数取值为 [0, 13] .

题目样例
示例
输入: [1,2,3,4,5]
输出: True

输入: [0,0,1,2,5]
输出: True

class Solution(object):
    def isStraight(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        joker = 0
        nums.sort()
        for i in range(4):
            if nums[i] == 0:
                joker += 1
            elif nums[i] == nums[i + 1]:
                return False
        return nums[4] - nums[joker] < 5

1047. 删除字符串中的所有相邻重复项

输入:“abbaca”
输出:“ca”
先删除bb,后剩aaca,再删除aa,剩ca为输出

class Solution(object):
    def removeDuplicates(self, s):
        """
        :type s: str
        :rtype: str
        """
        stack = []
 
        # 以下注释的写法存在问题
        # 对第一个字符的处理问题,应该保证第一个元素一定进去stack
        # 以下注释的写法导致对空栈进行pop
        # for i in s:
        #     if stack and stack[-1] != i:
        #         stack.append(i)
        #     else:
        #         # stack需要pop
        #         stack.pop()
 
        for i in s:
            if stack and stack[-1] == i:
                stack.pop()
            else:
                # stack需要pop
                stack.append(i)
        # return stack 不能直接return stack,输出应该是字符串
        # 拿字符串直接作为栈,省去了栈还要转为字符串的操作。
        # stack是列表实现的,可用join实现字符串否则应该先将stack中元素pop后再反转顺序输出
        return ''.join(stack) 

673. 最长递增子序列的个数

给定一个未排序的整数数组,找到最长递增子序列的个数。

示例 1:
输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

示例 2:
输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if len(nums) <= 1:
            return len(nums)
        dp = [1] * len(nums)
        result = 1
        for i in range(1, len(nums)):
            for j in range(0, i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
            result = max(result, dp[i]) #取长的子序列
        return result

1444. 切披萨的方案数

253. 会议室 II

给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。

示例 1:
输入:intervals = [[0,30],[5,10],[15,20]]
输出:2

示例 2:
输入:intervals = [[7,10],[2,4]]
输出:1

提示:
1 <= intervals.length <= 104
0 <= starti < endi <= 106

import heapq # heapq库中的堆默认是最小堆(树中各个父节点的值总是小于或等于任何一个子节点的值)
def minMeetingRooms(intervals):
    intervals.sort() # 按开始时间排序(按各数组的第一个元素大小排序)
    heap = []  # 结束时间堆
    ans = 0
    for interval in intervals:
        left, right = interval # 如interval:[0,30],则left:0;right:30
        while heap and heap[0] <= left: # heap[0]是堆中的最小值
            heapq.heappop(heap) # 弹出堆heap中最小值(删除并返回最小值,因为堆的特征是heap[0]永远是最小的元素,所以一般都是删除第一个元素)
        heapq.heappush(heap, right) # 把元素right加入堆heap中
        ans = max(ans, len(heap))
    return ans

443. 压缩字符串

压缩后的长度必须始终小于或等于原数组长度。
数组的每个元素应该是长度为1 的字符(不是 int 整数类型)。
在完成原地修改输入数组后,返回数组的新长度。

进阶:
你能否仅使用O(1) 空间解决问题?

示例 1:
输入:
[“a”,“a”,“b”,“b”,“c”,“c”,“c”]
输出:
返回6,输入数组的前6个字符应该是:[“a”,“2”,“b”,“2”,“c”,“3”]
说明:
"aa"被"a2"替代。"bb"被"b2"替代。"ccc"被"c3"替代。

示例 2:
输入:
[“a”]
输出:
返回1,输入数组的前1个字符应该是:[“a”]
说明:
没有任何字符串被替代。

示例 3:
输入:
[“a”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”]
输出:
返回4,输入数组的前4个字符应该是:[“a”,“b”,“1”,“2”]。

说明:
由于字符"a"不重复,所以不会被压缩。"bbbbbbbbbbbb"被“b12”替代。
注意每个数字在数组中都有它自己的位置。

解析:

1.首先构建一个字符串用于存放字符和其重复的次数
2.遍历字符串,碰到连续的字符计算其连续次数,如果连续相同字符的个数大于1则还需要把字符串的次数统计减进去
3.最后把构建好的char_str,list一下并顺序用char_str中的字符改变chars对应位置的字符并返回char_str的长度

class Solution:
    def compress(self, chars):
        """
        :type chars: List[str]
        :rtype: int
        """
        strr = ""
        count = 1
        i = 1
        c_len = len(chars)
        if c_len == 1:
            return c_len
        for i in range(1,c_len):
            if chars[i-1] == chars[i]:
                count += 1
            else:
                if count >1:
                    strr += chars[i-1] + str(count)
                else:
                    strr += chars[i-1] 
                count = 1    
        if count >1:
            strr += chars[i] + str(count)
        else:
            strr += chars[i]
        strr = list(strr)
        chars[::] = strr[::]
        return len(strr)
  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值