文章目录
31、从1到n的整数中1出现的个数
比如,1-13中,1出现6次,分别是1,10,11,12,13。
31.1 思路
- 除以10 遍历
1- 一个数和10求余等于1则记录一次
2- 再将这个数整除10,再判断一条件
31.2 解题
def NumberOf1Between1AndN_Solution(n):
res = 0
for i in range(1, n+1):
while i != 0:
if i % 10 == 1:
res += 1
i //= 10
return res
"""
>>> NumberOf1Between1AndN_Solution(13)
6
"""
32、把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323
32.1 思路
- 遍历 对数组内的数字两两比较,寻找str1str2,和str2str1两种组合中较小的一个。
1- 先找到最小的前面两个组合
2- 再去找后续最小的两个组合
3- 每次调换顺序,最后合并
例如:
[123, 1, 9]
1-
1-1 比较 1231 & 1123 -->> 1123 -> [1, 123, 9]
1-2 比较 19 & 91 -->> 19 -> [1, 123, 9]
2- 已经确认第一位
2-1 比较 1239 & 9123 -->> 1239 -> [1, 123, 9]
-------- >>> [1, 123, 9] --> 11239
32.2 解题
def PrintMinNumber(numbers):
if not numbers:
return ''
len_num = len(numbers)
# 1- 转文本
numbers = [str(num) for num in numbers]
#
for i in range(len_num-1): # 从头遍历
for j in range(i+1, len_num): # 从+1开始
# 依次次比较组合,然后调换位置,一一比较
if numbers[j] + numbers[i] < numbers[i] + numbers[j]:
numbers[i],numbers[j] = numbers[j],numbers[i]
return ''.join(numbers)
"""
>>> PrintMinNumber([123, 1, 9] )
'11239'
"""
33、丑数
把只包含质因子2、3和5的数称作丑数。例如6、8都是丑数,但14不是,因为它包含质因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
33.1 思路
- 逐步生成丑数
1- 每次生成3个丑数
2- 最小的那个就是需要的第n个丑数
3- 计数器(loop_n
)作为循环限制
例如:
第6个丑数
1- loop_n = 5, res = 1
1-1 增加(res)1 * 丑数因子[2, 3, 5] -> ugly_set = {2,3,5}
取最小 -> res = 2
去最小 -> {3, 5}
loop_n = 4
1-2 增加(res)2 * 丑数因子[4, 6, 10] -> ugly_set = {3,5, 4, 6, 10}
取最小 -> res = 3
去最小 -> {5, 4, 6, 10}
loop_n = 3
1-3 增加(res)3 * 丑数因子[6, 9, 15] -> ugly_set = {5, 4, 6, 10, 9, 15}
取最小 -> res = 4
去最小 -> {5, 6, 10, 9, 15}
loop_n = 2
1-4 增加(res)4 * 丑数因子[8, 12, 20] -> ugly_set = {5, 6, 10, 9, 15, 8, 12, 20}
取最小 -> res = 5
去最小 -> {6, 10, 9, 15, 8, 12, 20}
loop_n = 1
1-5 增加(res)5 * 丑数因子[10, 15, 25] -> ugly_set = {6, 10, 9, 15, 8, 12, 25}
取最小 -> res = 6
去最小 -> {10, 9, 15, 8, 12, 25}
loop_n = 0 -> 停止循环
—>>> res = 6
33.2 解题
def GetUglyNumber_Solution(n):
if n == 0:
return 0
loop_n = n - 1
res = 1
ugly_set = set()
while loop_n:
loop_n -= 1
ugly_set.add(2 * res)
ugly_set.add(3 * res)
ugly_set.add(5 * res)
res = min(ugly_set)
ugly_set.remove(res)
return res
"""
>>> GetUglyNumber_Solution(6)
6
"""
34、第一个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)。
34.1 思路
- 思路:创建哈希表,下标为ACII值,值为出现次数。
34.2 解题
def FirstNotRepeatingChar(s):
ls = [0]*256 # ASCII值
for i in s:
ls[ord(i)] += 1
for j in s:
if ls[ord(j)] == 1:
return s.index(j)
break
return -1
35、数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
35.1 思路
- 思路:暴力双指针遍历
35.2 解题
def InversePairs(lst):
n = len(lst)
cnt = 0
for i in range(n-1):
for j in range(i + 1, n):
if lst[i] > lst[j]:
cnt += 1
return cnt%1000000007
"""
>>> InversePairs([3, 5, 6 ,1, 2, 3])
8
"""
37、统计一个数字在排序数组中的出现的次数
考虑数组为空的情况,直接返回0
37.1 思路
- 思路:暴力双指针遍历
37.2 解题
def GetNumberOfK(lst, k):
loop_times = 0
if lst == []:
return 0
if (lst[0] > k) or (lst[-1] < k):
return 0
n = len(lst)
i, j = 0, n-1
while i < j and lst[i] != lst[j]:
loop_times += 1
if lst[i] != k:
i += 1
if lst[j] != k:
j -= 1
print(f'loop times: {loop_times}')
return j - i + 1
"""
>>> GetNumberOfK([1,1,2,3,4,5,6,6,7], 1)
2
"""
37.3 优化解题
- 思路: 已经排好序,
1- 二分定位
2- 小范围双指针遍历
def GetNumberOfK_fst(lst, k):
if lst == []:
return 0
if (lst[0] > k) or (lst[-1] < k):
return 0
n = len(lst)
i, j = 0, n-1
loop_times = 0
print(f'now the range of lst: [{i} , {j}]' )
# 1- 缩小区间
while i < j:
loop_times += 1
tmp_i , tmp_j = i, j
if lst[(j - i)//2] < k :
i = (j - i)//2
elif lst[(j - i)//2] > k :
j = (j - i)//2
# 当中间位置就是需要查找的值的时候退出
if tmp_i == i and tmp_j == j:
# 当中间值刚刚好是最后个或第一个K时
if lst[(j - i)//2] == k and lst[(j - i)//2 + 1] > k:
j = (j - i)//2
if lst[(j - i)//2] == k and lst[(j - i)//2 - 1] < k:
i = (j - i)//2
break
print(f'now the range of lst: [{i} , {j}]' )
# 2- 小范围内遍历
while i < j and lst[i] != lst[j]:
loop_times += 1
if lst[i] != k:
i += 1
if lst[j] != k:
j -= 1
print(f'loop times: {loop_times}')
return j - i +1
"""
>>> lst = [1]*9 + [2]*10 + [3]*10000
>>> GetNumberOfK_fst(lst, 1)
now the range of lst: [0 , 10018]
now the range of lst: [0 , 9]
loop times: 12
9
>>> GetNumberOfK(lst, 1)
loop times: 10010
9
>>> lst = [1]*9999 + [3]*10001
>>> GetNumberOfK_fst(lst, 3)
now the range of lst: [0 , 19999]
now the range of lst: [9999 , 19999]
loop times: 1
10001
>>> GetNumberOfK(lst, 3)
loop times: 9999
10001
"""
38、二叉树的深度
38.1 思路
- 思路:递归,左右遍历取最长的
38.2 解题
def maxDepth(tree):
if tree is None:
return 0
# 每递归计数一次
return max(maxDepth(tree.left), maxDepth(tree.right)) + 1
from scc_function.Struct import BTree
btree = BTree(1, left = BTree(2, left=BTree(1), right=BTree(1))
, right = BTree(2, left=BTree(1, left=BTree(1)), right=BTree(1)))
"""
>>> maxDepth(btree)
4
"""
39、平衡二叉树
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
39.1 思路
- 思路:递归
1- 利用
39的求数深的函数
求得左右子树的深度
2- 比较两者的差值,大于1为非平衡树
39.2 解题
def isBalanced(tree):
if tree is None:
return True
elif abs(maxDepth(tree.left)-maxDepth(tree.right))>1:
return False
else:
return isBalanced(tree.left) and isBalanced(tree.right)
btree = BTree(1, left = BTree(2, left=BTree(1)
, right=BTree(1))
, right = BTree(2, left=BTree(1
, left=BTree(1,
left=BTree(1)))
, right=BTree(1)))
"""
>>> isBalanced(btree)
False
"""
40、数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字
# 当只有一个数字不一样的时候:
def FindNumsAppearOnce(lst):
a = 0
for num in lst:
a ^= num
return a
"""
>>> FindNumsAppearOnce([1,1,2,3,3,4,4])
2
"""
40.1 思路
- 思路:遍历
1- 字典遍历并记录
2- 最后在遍历字典,将只出现一次的记录列表,最后返回
40.2 解题
def FindNumsAppearOnce(lst):
dct = {}
res = []
for i in lst:
dct[i] = dct[i] + 1 if i in dct else 1
for k, v in dct.items():
if v == 1:
res.append(k)
return res
"""
>>> lst = [1,1,2,2,4,4,5,6,7,7,8,8]
>>> FindNumsAppearOnce(lst)
[5, 6]
"""
40.3 优化
- 思路:存在一个不一样的数字的时候可以用异或,那么分成两个数组就能同样的操作了
1- 找到拆分成两个子列表的值(该数和其中一个目标数的并为0)
1-1 异或之后末尾为1,则两个数的最右是不一样的,即a & 1 == 1
,这时候可以用1做拆分
1-2 异或之后末尾为0, 则两个数的最右是一样的,即a & 1 == 0
这时候用1拆分就会存在问题,所以这时候需要向左移动直到找到a & x == 1
2- 分两组做异或
def FindNumsAppearOnce(lst):
a = 0
for num in lst:
a ^= num
splitBit = 1
while splitBit & a == 0:
splitBit = splitBit << 1
print(splitBit)
res1 = 0
res2 = 0
for i in lst:
if i & splitBit == 0:
res1 ^= i
else:
res2 ^= i
return [res1,res2]
41、和为S的连续正数序列
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。
41.1 思路
- 思路:双指针偏移
1- 现在最小正数定下头指针(1)和尾指针(2)
2- 比较当前和累计与目标值的大小
2-1 当当前累计等于目标值的时候输出, 头指针到尾指针的序列,尾指针后移,并加上后移的值
2-2 当当前累计大于目标值的时候, 头指针后移,并减去之前的值
2-3 当当前累计小于目标值的时候, 尾指针后移,并加上后移的值
2-4 当头指针到达目标值一半以上的时候停止
41.2 解题
def findlist(tsum):
res = []
i, j = 1, 2
csum = i + j
while i <= tsum/2:
if csum == tsum:
res.append(list(range(i, j+1)))
j += 1
csum += j
elif csum > tsum:
csum -= i
i += 1
else:
j += 1
csum += j
return res
"""
>>> findlist(30)
[[4, 5, 6, 7, 8], [6, 7, 8, 9], [9, 10, 11]]
"""
42、和为S的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的
42.1 思路
- 思路:双指针变量
1- 由于已经排好序,需要乘积最小,那么两者距离越远乘积越小
2- 当头+尾指针的和大于目标值的时候,移动尾指针
3- 当头+尾指针的和小于目标值的时候,移动头指针
41.2 解题
def FindNumbersWithSum(array, tsum):
n = len(array)
if n < 2:
return []
i = 0
j = n - 1
while i < j:
if array[i] + array[j] > tsum:
j -= 1
elif array[i] + array[j] < tsum:
i += 1
else:
return [array[i],array[j]]
return []
"""
>>> FindNumbersWithSum([1,2,3,4,5,8], 6)
[1, 5]
"""
43、左旋转字符
对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”
43.1 思路
- 思路:切片
43.2 解题
def leftturn(s, n):
return s[n:] + s[:n]
"""
>>> leftturn('abcXYZdef', 3)
'XYZdefabc'
"""
44、翻转单词顺序列
例如,“student. a am I”翻转为“I am a student.”。
44.1 思路
- 思路:split join
44.2 解题
def IsContinuous(s):
s = s.split(' ')
s.reverse()
return ' '.join(s)
45、扑克牌顺子
一副扑克牌,里面有2个大王,2个小王,从中随机抽出5张牌,如果牌能组成顺子就输出true,否则就输出false。为了方便起见,大小王是0,大小王可以当作任何数字。
45.1 思路
- 思路:遍历(共5张牌)
1- 找出大王小王的数量
2- 相邻的相等False, 存在2返回False
45.2 解题
def IsContinuous(lst):
"""
随机抽5张牌
"""
if not lst or lst==[]:
return False
trans_dct = {'J':11, 'Q':12, 'K':13, 'A':14
,'king_big':0, 'king_small':0
}
lst_deal = [trans_dct[i] if i in trans_dct else i for i in lst ]
lst_deal = sorted(lst_deal)
king_cnt = 0
while lst_deal[king_cnt] == 0:
king_cnt += 1
gap_cnt = 0
for i in range(king_cnt, 4):
if (lst_deal[i+1] == lst_deal[i]) or (lst_deal[i] == 2):
return False
gap_cnt += lst_deal[i+1] - lst_deal[i] - 1 # 相差1的不算间隙
return True if gap_cnt <= king_cnt else False
"""
>>> IsContinuous(['J', 'Q', 'K', 'king_big', 2])
False
>>> IsContinuous(['J', 'Q', 'K', 'king_big', 'A'])
True
"""
46、孩子们的游戏(圆圈中最后剩下的数)
游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列,不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友获胜,获胜的小朋友编号多少?(注:小朋友的编号是从0到n-1)
46.1 思路
- 思路:求余
移动位置求余 来排除
46.2 解题
def LastRemaining_Solution(n, m):
if not n and not m :
return -1
lst = list(range(n))
i = 0
while len(lst) > 1:
# 移动位置求余
i = (m-1 + i) % len(lst)
lst.pop(i)
return lst[0]
"""
>>> LastRemaining_Solution(5, 3)
3
"""
47、求1+2+3+…+n
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
47.1 思路
- 思路:递归
47.2 解题
def Sum_Solution(n):
"""
不能使用乘除法,
使用if else来确定终止条件
利用and来实现递归的终止
"""
return n and n + Sum_Solution(n-1)
"""
>>> Sum_Solution(100)
5050
"""
48、不用加减乘除做加法
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
48.1 思路
- 思路:位运算
1-可以考虑将加法分成两部分 (1)0+1 = 1 1+0 = 1; (2) 0+0=0 1+1=10
2- (1)部分的运算其实和 ^ (异或一致) 01|10-> 1 00|11->0
3- (2)部分可以分解成 & + << 00-> 0 << 1 -> 00; 11-> 1 << 1 -> 10
4- 循环直到(2)部分的数为0
48.2 解题
def Add(num1, num2):
while num2 !=0:
sum_ = num1 ^ num2
sum_add = (num1 & num2) << 1
num1 = sum_
num2 = sum_add
return sum_
"""
>>> Add(100,1000)
1100
"""
48.3 优化
知乎博主指出python的负数相加需要特殊处理,否则会陷入死循环
在早期版本中如Python2.7中,整数的有int和long两个类型。int类型是一个固定位数的数;long则是一个理论上可以存储无限大数的数据类型。当数大到可能溢出时,为了避免溢出,python会把int转化为long。而Python3.x之后整数只有一个可以放任意大数的int了。可是无论哪种,都是采用了特殊的方法实现了不会溢出的大整数。 所以会使程序无限的算下去,这也是Python效率低的一个原因。(python2和python3都有这个问题。)
已经知道了右移过程中大整数的自动转化,导致变不成0,那么只需要在移动的过程中加一下判断就行了,把sum_add的值和0xFFFFFFFF做一下比较就可以了,具体代码如下所示
def Add(num1, num2):
while num2 !=0:
sum_ = num1 ^ num2
sum_add = 0xFFFFFFFF&(num1 & num2)<<1
sum_add = -(~(sum_add - 1) & 0xFFFFFFFF) if sum_add > 0x7FFFFFFF else sum_add
num1 = sum_
num2 = sum_add
return sum_
"""
>>> Add(100,-1)
99
"""
49、把字符串转换成整数
将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),
要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
49.1 思路
- 思路:int
1- 先去除两边空值,然后判断正负
2- 用try except输出
49.2 解题
def StrToInt(s):
s = s.strip()
if not s:
return 0
symbol = 1
if s[0] == '-':
s = s[1:]
symbol = -1
if s[0] == '+':
s = s[1:]
num_dct = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
out_num = 0
n = 1
for i in s[::-1]:
if i in num_dct:
out_num += n * num_dct[i]
n *= 10
else:
return 0
return out_num * symbol
## 可以直接用int 在内会自己判断
def StrToInt(s):
try:
return int(s)
except:
return 0
50、数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。
也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
50.1 思路
- 思路:字典+遍历
1- 当某个数字出现了两次就停止
50.2 解题
def duplicate(numbers, duplication):
cnt_dct = {}
loop_n = 0
# 仅仅当某个数字出现2次就停止
for i in numbers:
cnt_dct[i] = cnt_dct.get(i,0) + 1
if cnt_dct[i] > 1:
duplication[0] = i
print(f'获取重复数字循环了:{loop_n}次')
return True
loop_n += 1
print(f'获取重复数字循环了:{loop_n}次')
return False
50.3 优化解题
- 双指针遍历
def duplicate_qk(numbers, duplication):
cnt_dct = {}
loop_n = 0
n = len(numbers)
i, j = 0, n - 1
# 仅仅当某个数字出现2次就停止
while i < j:
num_i, num_j = numbers[i], numbers[j]
cnt_dct[num_i] = cnt_dct.get(num_i, 0) + 1
cnt_dct[num_j] = cnt_dct.get(num_j, 0) + 1
if cnt_dct[num_i] > 1:
duplication[0] = num_i
print(f'获取重复数字循环了:{loop_n}次')
return True
if cnt_dct[num_j] > 1:
duplication[0] = num_j
print(f'获取重复数字循环了:{loop_n}次')
return True
i += 1
j -= 1
loop_n += 1
print(f'获取重复数字循环了:{loop_n}次')
return False
- 测试比较
import numpy as np
for _ in range(5):
# 产生20000个0-10000的随机数列表
hundred_lst = list(range(0,10000))
hundred_lst_plus = hundred_lst * 2
np.random.shuffle(hundred_lst_plus)
lst = [0]
print('\n', '--'*20)
print('单指针:')
a = duplicate(hundred_lst_plus, lst)
print('双指针:')
b = duplicate_qk(hundred_lst_plus, lst)
"""
----------------------------------------
单指针:
获取重复数字循环了:154次
双指针:
获取重复数字循环了:46次
----------------------------------------
单指针:
获取重复数字循环了:230次
双指针:
获取重复数字循环了:27次
----------------------------------------
单指针:
获取重复数字循环了:80次
双指针:
获取重复数字循环了:80次
----------------------------------------
单指针:
获取重复数字循环了:326次
双指针:
获取重复数字循环了:169次
----------------------------------------
单指针:
获取重复数字循环了:70次
双指针:
获取重复数字循环了:70次
"""
51、构建乘积数组
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]* A[1]* …* A[i-1]* A[i+1]* …* A[n-1]。不能使用除法。
51.1 思路
- 思路:遍历
1- 每一位都进行遍历
51.2 解题
# 构建乘积数组
def multiply(lst):
len_ = len(lst)
res_out = []
# 构建一个累积的数据
for index_r in range(len_):
res = 1
for index_ in range(len_):
if index_ == index_r:
continue
res *= lst[index_]
res_out.append(res)
return res_out
multiply([1, 2, 3])
51.3 解题2
- 2进制除法
参考LeetCode 两数相除
def multiply_2(lst):
all_mult = 1
for i in lst:
all_mult *= i
res_out = []
for num in lst:
sign_ = (all_mult > 0) ^(num > 0)
a, b = abs(num), abs(all_mult)
# 获取第一次向左移动的位数
cnt = 0
while a <= b:
a <<= 1
cnt += 1
# 开始计算'除法'
res_num = 0
while cnt > 0:
a >>= 1
cnt -= 1
if a <= b:
res_num += 1 << cnt
b -= a
# 获取除后的数
res_out.append(-res_num if sign_ else res_num)
return res_out
"""
>>> multiply_2([1, 2, -3])
[-6, -3, 2]
>>> multiply([1, 2, -3])
[-6, -3, 2]
"""
52、正则表达式匹配
请实现一个函数用来匹配包括’.‘和’ * ’ 的正则表达式。模式中的字符’.‘表示任意一个字符,而’ * '表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab * ac * a"匹配,但是与"aa.a"和"ab * a"均不匹配。
52.1 思路
- 思路:递归
1- 当text与parttern一致时返回True, 当pattern为空时, 返回 not text
2- 将’.‘与其他字母一并判断,并结合text是否为空
3- 发现并处理’*’ ,分两种情况后移,1) pattern后移两位,2) text后移一位
52.2 解题
def isMatch(text, pattern) -> bool:
if text == pattern:
return True
if not pattern:
return not text
match = bool(text) and pattern[0] in {'.', text[0]}
if len(pattern) >=2 and pattern[1] == '*':
# 匹配该字符0次,然后跳过该字符和'*', 一般在最后判断的时候(text已经为空)
# 当 pattern[0] 与 text[0]匹配后 后移 text
return isMatch(text, pattern[2:]) or\
(match and isMatch(text[1:], pattern))
return match and isMatch(text[1:], pattern[1:])
"""
>>> isMatch('asdasd', 'a.*')
True
"""
53、表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
53.1 思路
- 思路:float
53.2 解题
def isNumeric(self, s):
try:
s = float(s)
return True
except:
return False
54、字符流中第一个不重复的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
如果当前字符流没有存在出现一次的字符,返回#字符。
54.1 思路
- 思路:dct + 双指针遍历 (类似 50、数组中重复的数字)
54.2 解题
def FirstAppearingOnce(s):
cnt_dct = {}
n = len(s)
i, j = 0, n - 1
while i <= j:
s_i, s_j = s[i], s[j]
cnt_dct[s_i] = cnt_dct.get(s_i, 0) + 1
cnt_dct[s_j] = cnt_dct.get(s_j, 0) + 1
if i == j:
cnt_dct[s_j] = cnt_dct.get(s_j, 0) - 1
i += 1
j -= 1
for i in s:
if cnt_dct[i] == 1:
return i
return '#'
"""
>>> FirstAppearingOnce('gooogle')
{'g': 2, 'e': 1, 'o': 3, 'l': 1}
'l'
"""